You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/08/08 17:57:00 UTC

[10/17] incubator-freemarker git commit: Renamed XxxUtil classes to XxxUtils, as this convention is more widespread nowadays.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtil.java
deleted file mode 100644
index 727085f..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtil.java
+++ /dev/null
@@ -1,545 +0,0 @@
-/*
- * 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.Date;
-
-import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
-import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
-import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateScalarModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
-import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.valueformat.TemplateDateFormat;
-import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
-import org.apache.freemarker.core.valueformat.TemplateValueFormat;
-import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
-
-/**
- * Internally used static utilities for evaluation expressions.
- */
-public class _EvalUtil {
-    static final int CMP_OP_EQUALS = 1;
-    static final int CMP_OP_NOT_EQUALS = 2;
-    static final int CMP_OP_LESS_THAN = 3;
-    static final int CMP_OP_GREATER_THAN = 4;
-    static final int CMP_OP_LESS_THAN_EQUALS = 5;
-    static final int CMP_OP_GREATER_THAN_EQUALS = 6;
-    // If you add a new operator here, update the "compare" and "cmpOpToString" methods!
-    
-    // Prevents instantination.
-    private _EvalUtil() { }
-    
-    /**
-     * @param expr {@code null} is allowed, but may results in less helpful error messages
-     * @param env {@code null} is allowed
-     */
-    static String modelToString(TemplateScalarModel model, ASTExpression expr, Environment env)
-    throws TemplateModelException {
-        String value = model.getAsString();
-        if (value == null) {
-            throw newModelHasStoredNullException(String.class, model, expr);
-        }
-        return value;
-    }
-    
-    /**
-     * @param expr {@code null} is allowed, but may results in less helpful error messages
-     */
-    static Number modelToNumber(TemplateNumberModel model, ASTExpression expr)
-        throws TemplateModelException {
-        Number value = model.getAsNumber();
-        if (value == null) throw newModelHasStoredNullException(Number.class, model, expr);
-        return value;
-    }
-
-    /**
-     * @param expr {@code null} is allowed, but may results in less helpful error messages
-     */
-    static Date modelToDate(TemplateDateModel model, ASTExpression expr)
-        throws TemplateModelException {
-        Date value = model.getAsDate();
-        if (value == null) throw newModelHasStoredNullException(Date.class, model, expr);
-        return value;
-    }
-    
-    /** Signals the buggy case where we have a non-null model, but it wraps a null. */
-    public static TemplateModelException newModelHasStoredNullException(
-            Class expected, TemplateModel model, ASTExpression expr) {
-        return new _TemplateModelException(expr,
-                _TemplateModelException.modelHasStoredNullDescription(expected, model));
-    }
-
-    /**
-     * Compares two expressions according the rules of the FTL comparator operators.
-     * 
-     * @param leftExp not {@code null}
-     * @param operator one of the {@code COMP_OP_...} constants, like {@link #CMP_OP_EQUALS}.
-     * @param operatorString can be null {@code null}; the actual operator used, used for more accurate error message.
-     * @param rightExp not {@code null}
-     * @param env {@code null} is tolerated, but should be avoided
-     */
-    static boolean compare(
-            ASTExpression leftExp,
-            int operator, String  operatorString,
-            ASTExpression rightExp,
-            ASTExpression defaultBlamed,
-            Environment env) throws TemplateException {
-        TemplateModel ltm = leftExp.eval(env);
-        TemplateModel rtm = rightExp.eval(env);
-        return compare(
-                ltm, leftExp,
-                operator, operatorString,
-                rtm, rightExp,
-                defaultBlamed, false,
-                false, false, false,
-                env);
-    }
-    
-    /**
-     * Compares values according the rules of the FTL comparator operators; if the {@link ASTExpression}-s are
-     * accessible, use {@link #compare(ASTExpression, int, String, ASTExpression, ASTExpression, Environment)} instead,
-     * as that gives better error messages.
-     * 
-     * @param leftValue maybe {@code null}, which will usually cause the appropriate {@link TemplateException}. 
-     * @param operator one of the {@code COMP_OP_...} constants, like {@link #CMP_OP_EQUALS}.
-     * @param rightValue maybe {@code null}, which will usually cause the appropriate {@link TemplateException}.
-     * @param env {@code null} is tolerated, but should be avoided
-     */
-    static boolean compare(
-            TemplateModel leftValue, int operator, TemplateModel rightValue,
-            Environment env) throws TemplateException {
-        return compare(
-                leftValue, null,
-                operator, null,
-                rightValue, null,
-                null, false,
-                false, false, false,
-                env);
-    }
-
-    /**
-     * Same as {@link #compare(TemplateModel, int, TemplateModel, Environment)}, but if the two types are incompatible,
-     *     they are treated as non-equal instead of throwing an exception. Comparing dates of different types will
-     *     still throw an exception, however.
-     */
-    static boolean compareLenient(
-            TemplateModel leftValue, int operator, TemplateModel rightValue,
-            Environment env) throws TemplateException {
-        return compare(
-                leftValue, null,
-                operator, null,
-                rightValue, null,
-                null, false,
-                true, false, false,
-                env);
-    }
-    
-    private static final String VALUE_OF_THE_COMPARISON_IS_UNKNOWN_DATE_LIKE
-            = "value of the comparison is a date-like value where "
-              + "it's not known if it's a date (no time part), time, or date-time, "
-              + "and thus can't be used in a comparison.";
-    
-    /**
-     * @param leftExp {@code null} is allowed, but may results in less helpful error messages
-     * @param operator one of the {@code COMP_OP_...} constants, like {@link #CMP_OP_EQUALS}.
-     * @param operatorString can be null {@code null}; the actual operator used, used for more accurate error message.
-     * @param rightExp {@code null} is allowed, but may results in less helpful error messages
-     * @param defaultBlamed {@code null} allowed; the expression to which the error will point to if something goes
-     *        wrong that is not specific to the left or right side expression, or if that expression is {@code null}.
-     * @param typeMismatchMeansNotEqual If the two types are incompatible, they are treated as non-equal instead
-     *     of throwing an exception. Comparing dates of different types will still throw an exception, however. 
-     * @param leftNullReturnsFalse if {@code true}, a {@code null} left value will not cause exception, but make the
-     *     expression {@code false}.  
-     * @param rightNullReturnsFalse if {@code true}, a {@code null} right value will not cause exception, but make the
-     *     expression {@code false}.  
-     */
-    static boolean compare(
-            TemplateModel leftValue, ASTExpression leftExp,
-            int operator, String operatorString,
-            TemplateModel rightValue, ASTExpression rightExp,
-            ASTExpression defaultBlamed, boolean quoteOperandsInErrors,
-            boolean typeMismatchMeansNotEqual,
-            boolean leftNullReturnsFalse, boolean rightNullReturnsFalse,
-            Environment env) throws TemplateException {
-        if (leftValue == null) {
-            if (leftNullReturnsFalse) { 
-                return false;
-            } else {
-                if (leftExp != null) {
-                    throw InvalidReferenceException.getInstance(leftExp, env);
-                } else {
-                    throw new _MiscTemplateException(defaultBlamed, env, 
-                                "The left operand of the comparison was undefined or null.");
-                }
-            }
-        }
-
-        if (rightValue == null) {
-            if (rightNullReturnsFalse) { 
-                return false;
-            } else {
-                if (rightExp != null) {
-                    throw InvalidReferenceException.getInstance(rightExp, env);
-                } else {
-                    throw new _MiscTemplateException(defaultBlamed, env,
-                                "The right operand of the comparison was undefined or null.");
-                }
-            }
-        }
-
-        final int cmpResult;
-        if (leftValue instanceof TemplateNumberModel && rightValue instanceof TemplateNumberModel) {
-            Number leftNum = _EvalUtil.modelToNumber((TemplateNumberModel) leftValue, leftExp);
-            Number rightNum = _EvalUtil.modelToNumber((TemplateNumberModel) rightValue, rightExp);
-            ArithmeticEngine ae =
-                    env != null
-                        ? env.getArithmeticEngine()
-                        : (leftExp != null
-                            ? leftExp.getTemplate().getArithmeticEngine()
-                            : BigDecimalArithmeticEngine.INSTANCE);
-            try {
-                cmpResult = ae.compareNumbers(leftNum, rightNum);
-            } catch (RuntimeException e) {
-                throw new _MiscTemplateException(defaultBlamed, e, env,
-                        "Unexpected error while comparing two numbers: ", e);
-            }
-        } else if (leftValue instanceof TemplateDateModel && rightValue instanceof TemplateDateModel) {
-            TemplateDateModel leftDateModel = (TemplateDateModel) leftValue;
-            TemplateDateModel rightDateModel = (TemplateDateModel) rightValue;
-            
-            int leftDateType = leftDateModel.getDateType();
-            int rightDateType = rightDateModel.getDateType();
-            
-            if (leftDateType == TemplateDateModel.UNKNOWN || rightDateType == TemplateDateModel.UNKNOWN) {
-                String sideName;
-                ASTExpression sideExp;
-                if (leftDateType == TemplateDateModel.UNKNOWN) {
-                    sideName = "left";
-                    sideExp = leftExp;
-                } else {
-                    sideName = "right";
-                    sideExp = rightExp;
-                }
-                
-                throw new _MiscTemplateException(sideExp != null ? sideExp : defaultBlamed, env,
-                        "The ", sideName, " ", VALUE_OF_THE_COMPARISON_IS_UNKNOWN_DATE_LIKE);
-            }
-            
-            if (leftDateType != rightDateType) {
-                throw new _MiscTemplateException(defaultBlamed, env,
-                        "Can't compare dates of different types. Left date type is ",
-                        TemplateDateModel.TYPE_NAMES.get(leftDateType), ", right date type is ",
-                        TemplateDateModel.TYPE_NAMES.get(rightDateType), ".");
-            }
-
-            Date leftDate = _EvalUtil.modelToDate(leftDateModel, leftExp);
-            Date rightDate = _EvalUtil.modelToDate(rightDateModel, rightExp);
-            cmpResult = leftDate.compareTo(rightDate);
-        } else if (leftValue instanceof TemplateScalarModel && rightValue instanceof TemplateScalarModel) {
-            if (operator != CMP_OP_EQUALS && operator != CMP_OP_NOT_EQUALS) {
-                throw new _MiscTemplateException(defaultBlamed, env,
-                        "Can't use operator \"", cmpOpToString(operator, operatorString), "\" on string values.");
-            }
-            String leftString = _EvalUtil.modelToString((TemplateScalarModel) leftValue, leftExp, env);
-            String rightString = _EvalUtil.modelToString((TemplateScalarModel) rightValue, rightExp, env);
-            // FIXME NBC: Don't use the Collator here. That's locale-specific, but ==/!= should not be.
-            cmpResult = env.getCollator().compare(leftString, rightString);
-        } else if (leftValue instanceof TemplateBooleanModel && rightValue instanceof TemplateBooleanModel) {
-            if (operator != CMP_OP_EQUALS && operator != CMP_OP_NOT_EQUALS) {
-                throw new _MiscTemplateException(defaultBlamed, env,
-                        "Can't use operator \"", cmpOpToString(operator, operatorString), "\" on boolean values.");
-            }
-            boolean leftBool = ((TemplateBooleanModel) leftValue).getAsBoolean();
-            boolean rightBool = ((TemplateBooleanModel) rightValue).getAsBoolean();
-            cmpResult = (leftBool ? 1 : 0) - (rightBool ? 1 : 0);
-        } else {
-            if (typeMismatchMeansNotEqual) {
-                if (operator == CMP_OP_EQUALS) {
-                    return false;
-                } else if (operator == CMP_OP_NOT_EQUALS) {
-                    return true;
-                }
-                // Falls through
-            }
-            throw new _MiscTemplateException(defaultBlamed, env,
-                    "Can't compare values of these types. ",
-                    "Allowed comparisons are between two numbers, two strings, two dates, or two booleans.\n",
-                    "Left hand operand ",
-                    (quoteOperandsInErrors && leftExp != null
-                            ? new Object[] { "(", new _DelayedGetCanonicalForm(leftExp), ") value " }
-                            : ""),
-                    "is ", new _DelayedAOrAn(new _DelayedFTLTypeDescription(leftValue)), ".\n",
-                    "Right hand operand ",
-                    (quoteOperandsInErrors && rightExp != null
-                            ? new Object[] { "(", new _DelayedGetCanonicalForm(rightExp), ") value " }
-                            : ""),
-                    "is ", new _DelayedAOrAn(new _DelayedFTLTypeDescription(rightValue)),
-                    ".");
-        }
-
-        switch (operator) {
-            case CMP_OP_EQUALS: return cmpResult == 0;
-            case CMP_OP_NOT_EQUALS: return cmpResult != 0;
-            case CMP_OP_LESS_THAN: return cmpResult < 0;
-            case CMP_OP_GREATER_THAN: return cmpResult > 0;
-            case CMP_OP_LESS_THAN_EQUALS: return cmpResult <= 0;
-            case CMP_OP_GREATER_THAN_EQUALS: return cmpResult >= 0;
-            default: throw new BugException("Unsupported comparator operator code: " + operator);
-        }
-    }
-
-    private static String cmpOpToString(int operator, String operatorString) {
-        if (operatorString != null) {
-            return operatorString;
-        } else {
-            switch (operator) {
-                case CMP_OP_EQUALS: return "equals";
-                case CMP_OP_NOT_EQUALS: return "not-equals";
-                case CMP_OP_LESS_THAN: return "less-than";
-                case CMP_OP_GREATER_THAN: return "greater-than";
-                case CMP_OP_LESS_THAN_EQUALS: return "less-than-equals";
-                case CMP_OP_GREATER_THAN_EQUALS: return "greater-than-equals";
-                default: return "???";
-            }
-        }
-    }
-
-    /**
-     * Converts a value to plain text {@link String}, or a {@link TemplateMarkupOutputModel} if that's what the
-     * {@link TemplateValueFormat} involved produces.
-     * 
-     * @param seqTip
-     *            Tip to display if the value type is not coercable, but it's sequence or collection.
-     * 
-     * @return Never {@code null}
-     */
-    static Object coerceModelToStringOrMarkup(TemplateModel tm, ASTExpression exp, String seqTip, Environment env)
-            throws TemplateException {
-        return coerceModelToStringOrMarkup(tm, exp, false, seqTip, env);
-    }
-    
-    /**
-     * @return {@code null} if the {@code returnNullOnNonCoercableType} parameter is {@code true}, and the coercion is
-     *         not possible, because of the type is not right for it.
-     * 
-     * @see #coerceModelToStringOrMarkup(TemplateModel, ASTExpression, String, Environment)
-     */
-    static Object coerceModelToStringOrMarkup(
-            TemplateModel tm, ASTExpression exp, boolean returnNullOnNonCoercableType, String seqTip, Environment env)
-            throws TemplateException {
-        if (tm instanceof TemplateNumberModel) {
-            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
-            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
-            try {
-                return assertFormatResultNotNull(format.format(tnm));
-            } catch (TemplateValueFormatException e) {
-                throw MessageUtil.newCantFormatNumberException(format, exp, e, false);
-            }
-        } else if (tm instanceof TemplateDateModel) {
-            TemplateDateModel tdm = (TemplateDateModel) tm;
-            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
-            try {
-                return assertFormatResultNotNull(format.format(tdm));
-            } catch (TemplateValueFormatException e) {
-                throw MessageUtil.newCantFormatDateException(format, exp, e, false);
-            }
-        } else if (tm instanceof TemplateMarkupOutputModel) {
-            return tm;
-        } else { 
-            return coerceModelToTextualCommon(tm, exp, seqTip, true, returnNullOnNonCoercableType, env);
-        }
-    }
-
-    /**
-     * Like {@link #coerceModelToStringOrMarkup(TemplateModel, ASTExpression, String, Environment)}, but gives error
-     * if the result is markup. This is what you normally use where markup results can't be used.
-     *
-     * @param seqTip
-     *            Tip to display if the value type is not coercable, but it's sequence or collection.
-     * 
-     * @return Never {@code null}
-     */
-    static String coerceModelToStringOrUnsupportedMarkup(
-            TemplateModel tm, ASTExpression exp, String seqTip, Environment env)
-            throws TemplateException {
-        if (tm instanceof TemplateNumberModel) {
-            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
-            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
-            try {
-                return ensureFormatResultString(format.format(tnm), exp, env);
-            } catch (TemplateValueFormatException e) {
-                throw MessageUtil.newCantFormatNumberException(format, exp, e, false);
-            }
-        } else if (tm instanceof TemplateDateModel) {
-            TemplateDateModel tdm = (TemplateDateModel) tm;
-            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
-            try {
-                return ensureFormatResultString(format.format(tdm), exp, env);
-            } catch (TemplateValueFormatException e) {
-                throw MessageUtil.newCantFormatDateException(format, exp, e, false);
-            }
-        } else { 
-            return coerceModelToTextualCommon(tm, exp, seqTip, false, false, env);
-        }
-    }
-
-    /**
-     * Converts a value to plain text {@link String}, even if the {@link TemplateValueFormat} involved normally produces
-     * markup. This should be used rarely, where the user clearly intend to use the plain text variant of the format.
-     * 
-     * @param seqTip
-     *            Tip to display if the value type is not coercable, but it's sequence or collection.
-     * 
-     * @return Never {@code null}
-     */
-    static String coerceModelToPlainText(TemplateModel tm, ASTExpression exp, String seqTip,
-            Environment env) throws TemplateException {
-        if (tm instanceof TemplateNumberModel) {
-            return assertFormatResultNotNull(env.formatNumberToPlainText((TemplateNumberModel) tm, exp, false));
-        } else if (tm instanceof TemplateDateModel) {
-            return assertFormatResultNotNull(env.formatDateToPlainText((TemplateDateModel) tm, exp, false));
-        } else {
-            return coerceModelToTextualCommon(tm, exp, seqTip, false, false, env);
-        }
-    }
-
-    /**
-     * @param tm
-     *            If {@code null} that's an exception
-     * 
-     * @param supportsTOM
-     *            Whether the caller {@code coerceModelTo...} method could handle a {@link TemplateMarkupOutputModel}.
-     *            
-     * @return Never {@code null}
-     */
-    private static String coerceModelToTextualCommon(
-            TemplateModel tm, ASTExpression exp, String seqHint, boolean supportsTOM, boolean returnNullOnNonCoercableType,
-            Environment env)
-            throws TemplateException {
-        if (tm instanceof TemplateScalarModel) {
-            return modelToString((TemplateScalarModel) tm, exp, env);
-        } else if (tm == null) {
-            if (exp != null) {
-                throw InvalidReferenceException.getInstance(exp, env);
-            } else {
-                throw new InvalidReferenceException(
-                        "Null/missing value (no more informatoin avilable)",
-                        env);
-            }
-        } else if (tm instanceof TemplateBooleanModel) {
-            // [FM3] This should be before TemplateScalarModel, but automatic boolean-to-string is only non-error since
-            // 2.3.20, so to keep backward compatibility we couldn't insert this before TemplateScalarModel.
-            boolean booleanValue = ((TemplateBooleanModel) tm).getAsBoolean();
-            return env.formatBoolean(booleanValue, false);
-        } else {
-            if (returnNullOnNonCoercableType) {
-                return null;
-            }
-            if (seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)) {
-                if (supportsTOM) {
-                    throw new NonStringOrTemplateOutputException(exp, tm, seqHint, env);
-                } else {
-                    throw new NonStringException(exp, tm, seqHint, env);
-                }
-            } else {
-                if (supportsTOM) {
-                    throw new NonStringOrTemplateOutputException(exp, tm, env);
-                } else {
-                    throw new NonStringException(exp, tm, env);
-                }
-            }
-        }
-    }
-
-    private static String ensureFormatResultString(Object formatResult, ASTExpression exp, Environment env)
-            throws NonStringException {
-        if (formatResult instanceof String) { 
-            return (String) formatResult;
-        }
-        
-        assertFormatResultNotNull(formatResult);
-        
-        TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) formatResult;
-        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                "Value was formatted to convert it to string, but the result was markup of ouput format ",
-                new _DelayedJQuote(mo.getOutputFormat()), ".")
-                .tip("Use value?string to force formatting to plain text.")
-                .blame(exp);
-        throw new NonStringException(null, desc);
-    }
-
-    static String assertFormatResultNotNull(String r) {
-        if (r != null) {
-            return r;
-        }
-        throw new NullPointerException("TemplateValueFormatter result can't be null");
-    }
-
-    static Object assertFormatResultNotNull(Object r) {
-        if (r != null) {
-            return r;
-        }
-        throw new NullPointerException("TemplateValueFormatter result can't be null");
-    }
-
-    static TemplateMarkupOutputModel concatMarkupOutputs(ASTNode parent, TemplateMarkupOutputModel leftMO,
-            TemplateMarkupOutputModel rightMO) throws TemplateException {
-        MarkupOutputFormat leftOF = leftMO.getOutputFormat();
-        MarkupOutputFormat rightOF = rightMO.getOutputFormat();
-        if (rightOF != leftOF) {
-            String rightPT;
-            String leftPT;
-            if ((rightPT = rightOF.getSourcePlainText(rightMO)) != null) {
-                return leftOF.concat(leftMO, leftOF.fromPlainTextByEscaping(rightPT));
-            } else if ((leftPT = leftOF.getSourcePlainText(leftMO)) != null) {
-                return rightOF.concat(rightOF.fromPlainTextByEscaping(leftPT), rightMO);
-            } else {
-                Object[] message = { "Concatenation left hand operand is in ", new _DelayedToString(leftOF),
-                        " format, while the right hand operand is in ", new _DelayedToString(rightOF),
-                        ". Conversion to common format wasn't possible." };
-                if (parent instanceof ASTExpression) {
-                    throw new _MiscTemplateException((ASTExpression) parent, message);
-                } else {
-                    throw new _MiscTemplateException(message);
-                }
-            }
-        } else {
-            return leftOF.concat(leftMO, rightMO);
-        }
-    }
-
-    /**
-     * Returns an {@link ArithmeticEngine} even if {@code env} is {@code null}, because we are in parsing phase.
-     */
-    static ArithmeticEngine getArithmeticEngine(Environment env, ASTNode tObj) {
-        return env != null
-                ? env.getArithmeticEngine()
-                : tObj.getTemplate().getParsingConfiguration().getArithmeticEngine();
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
new file mode 100644
index 0000000..199a88c
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
@@ -0,0 +1,545 @@
+/*
+ * 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.Date;
+
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateValueFormat;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+
+/**
+ * Internally used static utilities for evaluation expressions.
+ */
+public class _EvalUtils {
+    static final int CMP_OP_EQUALS = 1;
+    static final int CMP_OP_NOT_EQUALS = 2;
+    static final int CMP_OP_LESS_THAN = 3;
+    static final int CMP_OP_GREATER_THAN = 4;
+    static final int CMP_OP_LESS_THAN_EQUALS = 5;
+    static final int CMP_OP_GREATER_THAN_EQUALS = 6;
+    // If you add a new operator here, update the "compare" and "cmpOpToString" methods!
+    
+    // Prevents instantiation.
+    private _EvalUtils() { }
+    
+    /**
+     * @param expr {@code null} is allowed, but may results in less helpful error messages
+     * @param env {@code null} is allowed
+     */
+    static String modelToString(TemplateScalarModel model, ASTExpression expr, Environment env)
+    throws TemplateModelException {
+        String value = model.getAsString();
+        if (value == null) {
+            throw newModelHasStoredNullException(String.class, model, expr);
+        }
+        return value;
+    }
+    
+    /**
+     * @param expr {@code null} is allowed, but may results in less helpful error messages
+     */
+    static Number modelToNumber(TemplateNumberModel model, ASTExpression expr)
+        throws TemplateModelException {
+        Number value = model.getAsNumber();
+        if (value == null) throw newModelHasStoredNullException(Number.class, model, expr);
+        return value;
+    }
+
+    /**
+     * @param expr {@code null} is allowed, but may results in less helpful error messages
+     */
+    static Date modelToDate(TemplateDateModel model, ASTExpression expr)
+        throws TemplateModelException {
+        Date value = model.getAsDate();
+        if (value == null) throw newModelHasStoredNullException(Date.class, model, expr);
+        return value;
+    }
+    
+    /** Signals the buggy case where we have a non-null model, but it wraps a null. */
+    public static TemplateModelException newModelHasStoredNullException(
+            Class expected, TemplateModel model, ASTExpression expr) {
+        return new _TemplateModelException(expr,
+                _TemplateModelException.modelHasStoredNullDescription(expected, model));
+    }
+
+    /**
+     * Compares two expressions according the rules of the FTL comparator operators.
+     * 
+     * @param leftExp not {@code null}
+     * @param operator one of the {@code COMP_OP_...} constants, like {@link #CMP_OP_EQUALS}.
+     * @param operatorString can be null {@code null}; the actual operator used, used for more accurate error message.
+     * @param rightExp not {@code null}
+     * @param env {@code null} is tolerated, but should be avoided
+     */
+    static boolean compare(
+            ASTExpression leftExp,
+            int operator, String  operatorString,
+            ASTExpression rightExp,
+            ASTExpression defaultBlamed,
+            Environment env) throws TemplateException {
+        TemplateModel ltm = leftExp.eval(env);
+        TemplateModel rtm = rightExp.eval(env);
+        return compare(
+                ltm, leftExp,
+                operator, operatorString,
+                rtm, rightExp,
+                defaultBlamed, false,
+                false, false, false,
+                env);
+    }
+    
+    /**
+     * Compares values according the rules of the FTL comparator operators; if the {@link ASTExpression}-s are
+     * accessible, use {@link #compare(ASTExpression, int, String, ASTExpression, ASTExpression, Environment)} instead,
+     * as that gives better error messages.
+     * 
+     * @param leftValue maybe {@code null}, which will usually cause the appropriate {@link TemplateException}. 
+     * @param operator one of the {@code COMP_OP_...} constants, like {@link #CMP_OP_EQUALS}.
+     * @param rightValue maybe {@code null}, which will usually cause the appropriate {@link TemplateException}.
+     * @param env {@code null} is tolerated, but should be avoided
+     */
+    static boolean compare(
+            TemplateModel leftValue, int operator, TemplateModel rightValue,
+            Environment env) throws TemplateException {
+        return compare(
+                leftValue, null,
+                operator, null,
+                rightValue, null,
+                null, false,
+                false, false, false,
+                env);
+    }
+
+    /**
+     * Same as {@link #compare(TemplateModel, int, TemplateModel, Environment)}, but if the two types are incompatible,
+     *     they are treated as non-equal instead of throwing an exception. Comparing dates of different types will
+     *     still throw an exception, however.
+     */
+    static boolean compareLenient(
+            TemplateModel leftValue, int operator, TemplateModel rightValue,
+            Environment env) throws TemplateException {
+        return compare(
+                leftValue, null,
+                operator, null,
+                rightValue, null,
+                null, false,
+                true, false, false,
+                env);
+    }
+    
+    private static final String VALUE_OF_THE_COMPARISON_IS_UNKNOWN_DATE_LIKE
+            = "value of the comparison is a date-like value where "
+              + "it's not known if it's a date (no time part), time, or date-time, "
+              + "and thus can't be used in a comparison.";
+    
+    /**
+     * @param leftExp {@code null} is allowed, but may results in less helpful error messages
+     * @param operator one of the {@code COMP_OP_...} constants, like {@link #CMP_OP_EQUALS}.
+     * @param operatorString can be null {@code null}; the actual operator used, used for more accurate error message.
+     * @param rightExp {@code null} is allowed, but may results in less helpful error messages
+     * @param defaultBlamed {@code null} allowed; the expression to which the error will point to if something goes
+     *        wrong that is not specific to the left or right side expression, or if that expression is {@code null}.
+     * @param typeMismatchMeansNotEqual If the two types are incompatible, they are treated as non-equal instead
+     *     of throwing an exception. Comparing dates of different types will still throw an exception, however. 
+     * @param leftNullReturnsFalse if {@code true}, a {@code null} left value will not cause exception, but make the
+     *     expression {@code false}.  
+     * @param rightNullReturnsFalse if {@code true}, a {@code null} right value will not cause exception, but make the
+     *     expression {@code false}.  
+     */
+    static boolean compare(
+            TemplateModel leftValue, ASTExpression leftExp,
+            int operator, String operatorString,
+            TemplateModel rightValue, ASTExpression rightExp,
+            ASTExpression defaultBlamed, boolean quoteOperandsInErrors,
+            boolean typeMismatchMeansNotEqual,
+            boolean leftNullReturnsFalse, boolean rightNullReturnsFalse,
+            Environment env) throws TemplateException {
+        if (leftValue == null) {
+            if (leftNullReturnsFalse) { 
+                return false;
+            } else {
+                if (leftExp != null) {
+                    throw InvalidReferenceException.getInstance(leftExp, env);
+                } else {
+                    throw new _MiscTemplateException(defaultBlamed, env, 
+                                "The left operand of the comparison was undefined or null.");
+                }
+            }
+        }
+
+        if (rightValue == null) {
+            if (rightNullReturnsFalse) { 
+                return false;
+            } else {
+                if (rightExp != null) {
+                    throw InvalidReferenceException.getInstance(rightExp, env);
+                } else {
+                    throw new _MiscTemplateException(defaultBlamed, env,
+                                "The right operand of the comparison was undefined or null.");
+                }
+            }
+        }
+
+        final int cmpResult;
+        if (leftValue instanceof TemplateNumberModel && rightValue instanceof TemplateNumberModel) {
+            Number leftNum = _EvalUtils.modelToNumber((TemplateNumberModel) leftValue, leftExp);
+            Number rightNum = _EvalUtils.modelToNumber((TemplateNumberModel) rightValue, rightExp);
+            ArithmeticEngine ae =
+                    env != null
+                        ? env.getArithmeticEngine()
+                        : (leftExp != null
+                            ? leftExp.getTemplate().getArithmeticEngine()
+                            : BigDecimalArithmeticEngine.INSTANCE);
+            try {
+                cmpResult = ae.compareNumbers(leftNum, rightNum);
+            } catch (RuntimeException e) {
+                throw new _MiscTemplateException(defaultBlamed, e, env,
+                        "Unexpected error while comparing two numbers: ", e);
+            }
+        } else if (leftValue instanceof TemplateDateModel && rightValue instanceof TemplateDateModel) {
+            TemplateDateModel leftDateModel = (TemplateDateModel) leftValue;
+            TemplateDateModel rightDateModel = (TemplateDateModel) rightValue;
+            
+            int leftDateType = leftDateModel.getDateType();
+            int rightDateType = rightDateModel.getDateType();
+            
+            if (leftDateType == TemplateDateModel.UNKNOWN || rightDateType == TemplateDateModel.UNKNOWN) {
+                String sideName;
+                ASTExpression sideExp;
+                if (leftDateType == TemplateDateModel.UNKNOWN) {
+                    sideName = "left";
+                    sideExp = leftExp;
+                } else {
+                    sideName = "right";
+                    sideExp = rightExp;
+                }
+                
+                throw new _MiscTemplateException(sideExp != null ? sideExp : defaultBlamed, env,
+                        "The ", sideName, " ", VALUE_OF_THE_COMPARISON_IS_UNKNOWN_DATE_LIKE);
+            }
+            
+            if (leftDateType != rightDateType) {
+                throw new _MiscTemplateException(defaultBlamed, env,
+                        "Can't compare dates of different types. Left date type is ",
+                        TemplateDateModel.TYPE_NAMES.get(leftDateType), ", right date type is ",
+                        TemplateDateModel.TYPE_NAMES.get(rightDateType), ".");
+            }
+
+            Date leftDate = _EvalUtils.modelToDate(leftDateModel, leftExp);
+            Date rightDate = _EvalUtils.modelToDate(rightDateModel, rightExp);
+            cmpResult = leftDate.compareTo(rightDate);
+        } else if (leftValue instanceof TemplateScalarModel && rightValue instanceof TemplateScalarModel) {
+            if (operator != CMP_OP_EQUALS && operator != CMP_OP_NOT_EQUALS) {
+                throw new _MiscTemplateException(defaultBlamed, env,
+                        "Can't use operator \"", cmpOpToString(operator, operatorString), "\" on string values.");
+            }
+            String leftString = _EvalUtils.modelToString((TemplateScalarModel) leftValue, leftExp, env);
+            String rightString = _EvalUtils.modelToString((TemplateScalarModel) rightValue, rightExp, env);
+            // FIXME NBC: Don't use the Collator here. That's locale-specific, but ==/!= should not be.
+            cmpResult = env.getCollator().compare(leftString, rightString);
+        } else if (leftValue instanceof TemplateBooleanModel && rightValue instanceof TemplateBooleanModel) {
+            if (operator != CMP_OP_EQUALS && operator != CMP_OP_NOT_EQUALS) {
+                throw new _MiscTemplateException(defaultBlamed, env,
+                        "Can't use operator \"", cmpOpToString(operator, operatorString), "\" on boolean values.");
+            }
+            boolean leftBool = ((TemplateBooleanModel) leftValue).getAsBoolean();
+            boolean rightBool = ((TemplateBooleanModel) rightValue).getAsBoolean();
+            cmpResult = (leftBool ? 1 : 0) - (rightBool ? 1 : 0);
+        } else {
+            if (typeMismatchMeansNotEqual) {
+                if (operator == CMP_OP_EQUALS) {
+                    return false;
+                } else if (operator == CMP_OP_NOT_EQUALS) {
+                    return true;
+                }
+                // Falls through
+            }
+            throw new _MiscTemplateException(defaultBlamed, env,
+                    "Can't compare values of these types. ",
+                    "Allowed comparisons are between two numbers, two strings, two dates, or two booleans.\n",
+                    "Left hand operand ",
+                    (quoteOperandsInErrors && leftExp != null
+                            ? new Object[] { "(", new _DelayedGetCanonicalForm(leftExp), ") value " }
+                            : ""),
+                    "is ", new _DelayedAOrAn(new _DelayedFTLTypeDescription(leftValue)), ".\n",
+                    "Right hand operand ",
+                    (quoteOperandsInErrors && rightExp != null
+                            ? new Object[] { "(", new _DelayedGetCanonicalForm(rightExp), ") value " }
+                            : ""),
+                    "is ", new _DelayedAOrAn(new _DelayedFTLTypeDescription(rightValue)),
+                    ".");
+        }
+
+        switch (operator) {
+            case CMP_OP_EQUALS: return cmpResult == 0;
+            case CMP_OP_NOT_EQUALS: return cmpResult != 0;
+            case CMP_OP_LESS_THAN: return cmpResult < 0;
+            case CMP_OP_GREATER_THAN: return cmpResult > 0;
+            case CMP_OP_LESS_THAN_EQUALS: return cmpResult <= 0;
+            case CMP_OP_GREATER_THAN_EQUALS: return cmpResult >= 0;
+            default: throw new BugException("Unsupported comparator operator code: " + operator);
+        }
+    }
+
+    private static String cmpOpToString(int operator, String operatorString) {
+        if (operatorString != null) {
+            return operatorString;
+        } else {
+            switch (operator) {
+                case CMP_OP_EQUALS: return "equals";
+                case CMP_OP_NOT_EQUALS: return "not-equals";
+                case CMP_OP_LESS_THAN: return "less-than";
+                case CMP_OP_GREATER_THAN: return "greater-than";
+                case CMP_OP_LESS_THAN_EQUALS: return "less-than-equals";
+                case CMP_OP_GREATER_THAN_EQUALS: return "greater-than-equals";
+                default: return "???";
+            }
+        }
+    }
+
+    /**
+     * Converts a value to plain text {@link String}, or a {@link TemplateMarkupOutputModel} if that's what the
+     * {@link TemplateValueFormat} involved produces.
+     * 
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     * 
+     * @return Never {@code null}
+     */
+    static Object coerceModelToStringOrMarkup(TemplateModel tm, ASTExpression exp, String seqTip, Environment env)
+            throws TemplateException {
+        return coerceModelToStringOrMarkup(tm, exp, false, seqTip, env);
+    }
+    
+    /**
+     * @return {@code null} if the {@code returnNullOnNonCoercableType} parameter is {@code true}, and the coercion is
+     *         not possible, because of the type is not right for it.
+     * 
+     * @see #coerceModelToStringOrMarkup(TemplateModel, ASTExpression, String, Environment)
+     */
+    static Object coerceModelToStringOrMarkup(
+            TemplateModel tm, ASTExpression exp, boolean returnNullOnNonCoercableType, String seqTip, Environment env)
+            throws TemplateException {
+        if (tm instanceof TemplateNumberModel) {
+            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
+            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
+            try {
+                return assertFormatResultNotNull(format.format(tnm));
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtils.newCantFormatNumberException(format, exp, e, false);
+            }
+        } else if (tm instanceof TemplateDateModel) {
+            TemplateDateModel tdm = (TemplateDateModel) tm;
+            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
+            try {
+                return assertFormatResultNotNull(format.format(tdm));
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtils.newCantFormatDateException(format, exp, e, false);
+            }
+        } else if (tm instanceof TemplateMarkupOutputModel) {
+            return tm;
+        } else { 
+            return coerceModelToTextualCommon(tm, exp, seqTip, true, returnNullOnNonCoercableType, env);
+        }
+    }
+
+    /**
+     * Like {@link #coerceModelToStringOrMarkup(TemplateModel, ASTExpression, String, Environment)}, but gives error
+     * if the result is markup. This is what you normally use where markup results can't be used.
+     *
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     * 
+     * @return Never {@code null}
+     */
+    static String coerceModelToStringOrUnsupportedMarkup(
+            TemplateModel tm, ASTExpression exp, String seqTip, Environment env)
+            throws TemplateException {
+        if (tm instanceof TemplateNumberModel) {
+            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
+            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
+            try {
+                return ensureFormatResultString(format.format(tnm), exp, env);
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtils.newCantFormatNumberException(format, exp, e, false);
+            }
+        } else if (tm instanceof TemplateDateModel) {
+            TemplateDateModel tdm = (TemplateDateModel) tm;
+            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
+            try {
+                return ensureFormatResultString(format.format(tdm), exp, env);
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtils.newCantFormatDateException(format, exp, e, false);
+            }
+        } else { 
+            return coerceModelToTextualCommon(tm, exp, seqTip, false, false, env);
+        }
+    }
+
+    /**
+     * Converts a value to plain text {@link String}, even if the {@link TemplateValueFormat} involved normally produces
+     * markup. This should be used rarely, where the user clearly intend to use the plain text variant of the format.
+     * 
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     * 
+     * @return Never {@code null}
+     */
+    static String coerceModelToPlainText(TemplateModel tm, ASTExpression exp, String seqTip,
+            Environment env) throws TemplateException {
+        if (tm instanceof TemplateNumberModel) {
+            return assertFormatResultNotNull(env.formatNumberToPlainText((TemplateNumberModel) tm, exp, false));
+        } else if (tm instanceof TemplateDateModel) {
+            return assertFormatResultNotNull(env.formatDateToPlainText((TemplateDateModel) tm, exp, false));
+        } else {
+            return coerceModelToTextualCommon(tm, exp, seqTip, false, false, env);
+        }
+    }
+
+    /**
+     * @param tm
+     *            If {@code null} that's an exception
+     * 
+     * @param supportsTOM
+     *            Whether the caller {@code coerceModelTo...} method could handle a {@link TemplateMarkupOutputModel}.
+     *            
+     * @return Never {@code null}
+     */
+    private static String coerceModelToTextualCommon(
+            TemplateModel tm, ASTExpression exp, String seqHint, boolean supportsTOM, boolean returnNullOnNonCoercableType,
+            Environment env)
+            throws TemplateException {
+        if (tm instanceof TemplateScalarModel) {
+            return modelToString((TemplateScalarModel) tm, exp, env);
+        } else if (tm == null) {
+            if (exp != null) {
+                throw InvalidReferenceException.getInstance(exp, env);
+            } else {
+                throw new InvalidReferenceException(
+                        "Null/missing value (no more informatoin avilable)",
+                        env);
+            }
+        } else if (tm instanceof TemplateBooleanModel) {
+            // [FM3] This should be before TemplateScalarModel, but automatic boolean-to-string is only non-error since
+            // 2.3.20, so to keep backward compatibility we couldn't insert this before TemplateScalarModel.
+            boolean booleanValue = ((TemplateBooleanModel) tm).getAsBoolean();
+            return env.formatBoolean(booleanValue, false);
+        } else {
+            if (returnNullOnNonCoercableType) {
+                return null;
+            }
+            if (seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)) {
+                if (supportsTOM) {
+                    throw new NonStringOrTemplateOutputException(exp, tm, seqHint, env);
+                } else {
+                    throw new NonStringException(exp, tm, seqHint, env);
+                }
+            } else {
+                if (supportsTOM) {
+                    throw new NonStringOrTemplateOutputException(exp, tm, env);
+                } else {
+                    throw new NonStringException(exp, tm, env);
+                }
+            }
+        }
+    }
+
+    private static String ensureFormatResultString(Object formatResult, ASTExpression exp, Environment env)
+            throws NonStringException {
+        if (formatResult instanceof String) { 
+            return (String) formatResult;
+        }
+        
+        assertFormatResultNotNull(formatResult);
+        
+        TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) formatResult;
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Value was formatted to convert it to string, but the result was markup of ouput format ",
+                new _DelayedJQuote(mo.getOutputFormat()), ".")
+                .tip("Use value?string to force formatting to plain text.")
+                .blame(exp);
+        throw new NonStringException(null, desc);
+    }
+
+    static String assertFormatResultNotNull(String r) {
+        if (r != null) {
+            return r;
+        }
+        throw new NullPointerException("TemplateValueFormatter result can't be null");
+    }
+
+    static Object assertFormatResultNotNull(Object r) {
+        if (r != null) {
+            return r;
+        }
+        throw new NullPointerException("TemplateValueFormatter result can't be null");
+    }
+
+    static TemplateMarkupOutputModel concatMarkupOutputs(ASTNode parent, TemplateMarkupOutputModel leftMO,
+            TemplateMarkupOutputModel rightMO) throws TemplateException {
+        MarkupOutputFormat leftOF = leftMO.getOutputFormat();
+        MarkupOutputFormat rightOF = rightMO.getOutputFormat();
+        if (rightOF != leftOF) {
+            String rightPT;
+            String leftPT;
+            if ((rightPT = rightOF.getSourcePlainText(rightMO)) != null) {
+                return leftOF.concat(leftMO, leftOF.fromPlainTextByEscaping(rightPT));
+            } else if ((leftPT = leftOF.getSourcePlainText(leftMO)) != null) {
+                return rightOF.concat(rightOF.fromPlainTextByEscaping(leftPT), rightMO);
+            } else {
+                Object[] message = { "Concatenation left hand operand is in ", new _DelayedToString(leftOF),
+                        " format, while the right hand operand is in ", new _DelayedToString(rightOF),
+                        ". Conversion to common format wasn't possible." };
+                if (parent instanceof ASTExpression) {
+                    throw new _MiscTemplateException((ASTExpression) parent, message);
+                } else {
+                    throw new _MiscTemplateException(message);
+                }
+            }
+        } else {
+            return leftOF.concat(leftMO, rightMO);
+        }
+    }
+
+    /**
+     * Returns an {@link ArithmeticEngine} even if {@code env} is {@code null}, because we are in parsing phase.
+     */
+    static ArithmeticEngine getArithmeticEngine(Environment env, ASTNode tObj) {
+        return env != null
+                ? env.getArithmeticEngine()
+                : tObj.getTemplate().getParsingConfiguration().getArithmeticEngine();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluationException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluationException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluationException.java
index 620399a..6c8e700 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluationException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluationException.java
@@ -19,7 +19,7 @@
 
 package org.apache.freemarker.core;
 
-import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.util._StringUtils;
 
 /**
  * Don't use this; used internally by FreeMarker, might changes without notice.
@@ -38,7 +38,7 @@ public class _ObjectBuilderSettingEvaluationException extends Exception {
     public _ObjectBuilderSettingEvaluationException(String expected, String src, int location) {
         super("Expression syntax error: Expected a(n) " + expected + ", but "
                 + (location < src.length()
-                        ? "found character " + _StringUtil.jQuote("" + src.charAt(location)) + " at position "
+                        ? "found character " + _StringUtils.jQuote("" + src.charAt(location)) + " at position "
                             + (location + 1) + "."
                         : "the end of the parsed string was reached.") );
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/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
index 89a6fe8..7be4511 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -60,8 +60,8 @@ 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;
+import org.apache.freemarker.core.util._ClassUtils;
+import org.apache.freemarker.core.util._StringUtils;
 
 /**
  * Don't use this; used internally by FreeMarker, might changes without notice.
@@ -634,7 +634,7 @@ public class _ObjectBuilderSettingEvaluator {
                 if (i != 0) {
                     sb.append(" or ");
                 }
-                sb.append(_StringUtil.jQuote(expectedChars.substring(i, i + 1)));
+                sb.append(_StringUtils.jQuote(expectedChars.substring(i, i + 1)));
             }
             throw new _ObjectBuilderSettingEvaluationException(
                     sb.toString(),
@@ -739,13 +739,13 @@ public class _ObjectBuilderSettingEvaluator {
             if (!beanPropSetters.containsKey(name)) {
                 throw new _ObjectBuilderSettingEvaluationException(
                         "The " + cl.getName() + " class has no writeable JavaBeans property called "
-                        + _StringUtil.jQuote(name) + ".");
+                        + _StringUtils.jQuote(name) + ".");
             }
             
             Method beanPropSetter = beanPropSetters.put(name, null);
             if (beanPropSetter == null) {
                     throw new _ObjectBuilderSettingEvaluationException(
-                            "JavaBeans property " + _StringUtil.jQuote(name) + " is set twice.");
+                            "JavaBeans property " + _StringUtils.jQuote(name) + " is set twice.");
             }
             
             try {
@@ -765,7 +765,7 @@ public class _ObjectBuilderSettingEvaluator {
                 }
                 if (!(m instanceof JavaMethodModel)) {
                     throw new _ObjectBuilderSettingEvaluationException(
-                            _StringUtil.jQuote(beanPropSetter.getName()) + " wasn't a JavaMethodModel.");
+                            _StringUtils.jQuote(beanPropSetter.getName()) + " wasn't a JavaMethodModel.");
                 }
                 List/*TemplateModel*/ args = new ArrayList();
                 ((JavaMethodModel) m).execute(
@@ -773,7 +773,7 @@ public class _ObjectBuilderSettingEvaluator {
                         NonTemplateCallPlace.INSTANCE);
             } catch (Exception e) {
                 throw new _ObjectBuilderSettingEvaluationException(
-                        "Failed to set " + _StringUtil.jQuote(name), e);
+                        "Failed to set " + _StringUtils.jQuote(name), e);
             }
         }
     }
@@ -877,16 +877,16 @@ public class _ObjectBuilderSettingEvaluator {
             
             boolean clIsBuilderClass;
             try {
-                cl = _ClassUtil.forName(className + BUILDER_CLASS_POSTFIX_1);
+                cl = _ClassUtils.forName(className + BUILDER_CLASS_POSTFIX_1);
                 clIsBuilderClass = true;
             } catch (ClassNotFoundException eIgnored) {
                 try {
-                    cl = _ClassUtil.forName(className + BUILDER_CLASS_POSTFIX_2);
+                    cl = _ClassUtils.forName(className + BUILDER_CLASS_POSTFIX_2);
                     clIsBuilderClass = true;
                 } catch (ClassNotFoundException e) {
                     clIsBuilderClass = false;
                     try {
-                        cl = _ClassUtil.forName(className);
+                        cl = _ClassUtils.forName(className);
                     } catch (Exception e2) {
                         boolean failedToGetAsStaticField;
                         if (canBeStaticField && className.indexOf('.') != -1) {
@@ -901,7 +901,7 @@ public class _ObjectBuilderSettingEvaluator {
                             failedToGetAsStaticField = false;
                         }
                         throw new _ObjectBuilderSettingEvaluationException(
-                                "Failed to get class " + _StringUtil.jQuote(className)
+                                "Failed to get class " + _StringUtils.jQuote(className)
                                         + (failedToGetAsStaticField ? " (also failed to resolve name as static field)" : "")
                                         + ".",
                                 e2);
@@ -920,7 +920,7 @@ public class _ObjectBuilderSettingEvaluator {
                     // Expected
                 } catch (Exception e) {
                     throw new _ObjectBuilderSettingEvaluationException(
-                            "Error when trying to access " + _StringUtil.jQuote(className) + "."
+                            "Error when trying to access " + _StringUtils.jQuote(className) + "."
                             + INSTANCE_FIELD_NAME, e);
                 }
             }
@@ -944,10 +944,10 @@ public class _ObjectBuilderSettingEvaluator {
 
             Class<?> cl;
             try {
-                cl = _ClassUtil.forName(className);
+                cl = _ClassUtils.forName(className);
             } catch (Exception e) {
                 throw new _ObjectBuilderSettingEvaluationException(
-                        "Failed to get field's parent class, " + _StringUtil.jQuote(className) + ".",
+                        "Failed to get field's parent class, " + _StringUtils.jQuote(className) + ".",
                         e);
             }
             
@@ -956,8 +956,8 @@ public class _ObjectBuilderSettingEvaluator {
                 field = cl.getField(fieldName);
             } catch (Exception e) {
                 throw new _ObjectBuilderSettingEvaluationException(
-                        "Failed to get field " + _StringUtil.jQuote(fieldName) + " from class "
-                        + _StringUtil.jQuote(className) + ".",
+                        "Failed to get field " + _StringUtils.jQuote(fieldName) + " from class "
+                        + _StringUtils.jQuote(className) + ".",
                         e);
             }
             

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/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
index 76e9d2b..10ad99f 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateModelException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateModelException.java
@@ -21,7 +21,7 @@ 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;
+import org.apache.freemarker.core.util._ClassUtils;
 
 public class _TemplateModelException extends TemplateModelException {
 
@@ -126,7 +126,7 @@ public class _TemplateModelException extends TemplateModelException {
         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) } : ""),
+                (expected != null ? new Object[] { " instead of a ", _ClassUtils.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/ebb39b84/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
index 0d22656..d09d48f 100644
--- 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
@@ -21,7 +21,7 @@ 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;
+import org.apache.freemarker.core.util._NumberUtils;
 
 /**
  * Arithmetic engine that converts all numbers to {@link BigDecimal} and then operates on them, and also keeps the
@@ -40,37 +40,37 @@ public class BigDecimalArithmeticEngine extends ArithmeticEngine {
         // 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);
+        int firstSignum = _NumberUtils.getSignum(first);
+        int secondSignum = _NumberUtils.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);
+            BigDecimal left = _NumberUtils.toBigDecimal(first);
+            BigDecimal right = _NumberUtils.toBigDecimal(second);
             return left.compareTo(right);
         }
     }
 
     @Override
     public Number add(Number first, Number second) {
-        BigDecimal left = _NumberUtil.toBigDecimal(first);
-        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        BigDecimal left = _NumberUtils.toBigDecimal(first);
+        BigDecimal right = _NumberUtils.toBigDecimal(second);
         return left.add(right);
     }
 
     @Override
     public Number subtract(Number first, Number second) {
-        BigDecimal left = _NumberUtil.toBigDecimal(first);
-        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        BigDecimal left = _NumberUtils.toBigDecimal(first);
+        BigDecimal right = _NumberUtils.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 left = _NumberUtils.toBigDecimal(first);
+        BigDecimal right = _NumberUtils.toBigDecimal(second);
         BigDecimal result = left.multiply(right);
         if (result.scale() > maxScale) {
             result = result.setScale(maxScale, roundingPolicy);
@@ -80,8 +80,8 @@ public class BigDecimalArithmeticEngine extends ArithmeticEngine {
 
     @Override
     public Number divide(Number first, Number second) {
-        BigDecimal left = _NumberUtil.toBigDecimal(first);
-        BigDecimal right = _NumberUtil.toBigDecimal(second);
+        BigDecimal left = _NumberUtils.toBigDecimal(first);
+        BigDecimal right = _NumberUtils.toBigDecimal(second);
         return divide(left, right);
     }
 
@@ -94,7 +94,7 @@ public class BigDecimalArithmeticEngine extends ArithmeticEngine {
 
     @Override
     public Number toNumber(String s) {
-        return _NumberUtil.toBigDecimalOrDouble(s);
+        return _NumberUtils.toBigDecimalOrDouble(s);
     }
 
     private BigDecimal divide(BigDecimal left, BigDecimal right) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/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
index 12c27d9..16f58a2 100644
--- 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
@@ -27,7 +27,7 @@ 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;
+import org.apache.freemarker.core.util._NumberUtils;
 
 /**
  * Arithmetic engine that uses (more-or-less) the widening conversions of
@@ -96,8 +96,8 @@ public class ConservativeArithmeticEngine extends ArithmeticEngine {
                 return n1.compareTo(n2);
             }
             case BIG_DECIMAL: {
-                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
-                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                BigDecimal n1 = _NumberUtils.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtils.toBigDecimal(second);
                 return n1.compareTo(n2);
             }
         }
@@ -139,8 +139,8 @@ public class ConservativeArithmeticEngine extends ArithmeticEngine {
                 return n1.add(n2);
             }
             case BIG_DECIMAL: {
-                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
-                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                BigDecimal n1 = _NumberUtils.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtils.toBigDecimal(second);
                 return n1.add(n2);
             }
         }
@@ -182,8 +182,8 @@ public class ConservativeArithmeticEngine extends ArithmeticEngine {
                 return n1.subtract(n2);
             }
             case BIG_DECIMAL: {
-                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
-                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                BigDecimal n1 = _NumberUtils.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtils.toBigDecimal(second);
                 return n1.subtract(n2);
             }
         }
@@ -225,8 +225,8 @@ public class ConservativeArithmeticEngine extends ArithmeticEngine {
                 return n1.multiply(n2);
             }
             case BIG_DECIMAL: {
-                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
-                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                BigDecimal n1 = _NumberUtils.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtils.toBigDecimal(second);
                 BigDecimal r = n1.multiply(n2);
                 return r.scale() > maxScale ? r.setScale(maxScale, roundingPolicy) : r;
             }
@@ -274,8 +274,8 @@ public class ConservativeArithmeticEngine extends ArithmeticEngine {
                 }
             }
             case BIG_DECIMAL: {
-                BigDecimal n1 = _NumberUtil.toBigDecimal(first);
-                BigDecimal n2 = _NumberUtil.toBigDecimal(second);
+                BigDecimal n1 = _NumberUtils.toBigDecimal(first);
+                BigDecimal n2 = _NumberUtils.toBigDecimal(second);
                 int scale1 = n1.scale();
                 int scale2 = n2.scale();
                 int scale = Math.max(scale1, scale2);
@@ -319,8 +319,8 @@ public class ConservativeArithmeticEngine extends ArithmeticEngine {
 
     @Override
     public Number toNumber(String s) {
-        Number n = _NumberUtil.toBigDecimalOrDouble(s);
-        return n instanceof BigDecimal ? _NumberUtil.optimizeNumberRepresentation(n) : n;
+        Number n = _NumberUtils.toBigDecimalOrDouble(s);
+        return n instanceof BigDecimal ? _NumberUtils.optimizeNumberRepresentation(n) : n;
     }
 
     private static Map createClassCodesMap() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
index c1428c1..4efc482 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebuggerServer.java
@@ -33,7 +33,7 @@ import java.util.Arrays;
 import java.util.Random;
 
 import org.apache.freemarker.core.util.UndeclaredThrowableException;
-import org.apache.freemarker.core.util._SecurityUtil;
+import org.apache.freemarker.core.util._SecurityUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -54,9 +54,9 @@ class DebuggerServer {
     private ServerSocket serverSocket;
     
     public DebuggerServer(Serializable debuggerStub) {
-        port = _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.port", Debugger.DEFAULT_PORT).intValue();
+        port = _SecurityUtils.getSystemProperty("org.apache.freemarker.core.debug.port", Debugger.DEFAULT_PORT).intValue();
         try {
-            password = _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.password", "").getBytes(
+            password = _SecurityUtils.getSystemProperty("org.apache.freemarker.core.debug.password", "").getBytes(
                     StandardCharsets.UTF_8);
         } catch (UnsupportedCharsetException e) {
             throw new UndeclaredThrowableException(e);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
index 37d094c..4d37917 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/_DebuggerService.java
@@ -25,7 +25,7 @@ import java.util.List;
 
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.util._SecurityUtil;
+import org.apache.freemarker.core.util._SecurityUtils;
 
 /**
  * Don't use this; used internally by FreeMarker, might changes without notice.
@@ -40,7 +40,7 @@ public abstract class _DebuggerService {
         // off, this is a fast no-op service, otherwise it's the real-thing
         // RMI service.
         return 
-            _SecurityUtil.getSystemProperty("org.apache.freemarker.core.debug.password", null) == null
+            _SecurityUtils.getSystemProperty("org.apache.freemarker.core.debug.password", null) == null
             ? new NoOpDebuggerService()
             : new RmiDebuggerService();
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
index 3b346e6..77d1790 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ArgumentTypes.java
@@ -27,7 +27,7 @@ import java.util.List;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.util.BugException;
-import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._ClassUtils;
 
 /**
  * The argument types of a method call; usable as cache key.
@@ -152,7 +152,7 @@ final class ArgumentTypes {
      * specific than {@code Boolean}, because the former can't store {@code null}). The preferability decision gets
      * trickier when there's a possibility of numerical conversion from the actual argument type to the type of some of
      * the parameters. If such conversion is only possible for one of the competing parameter types, that parameter
-     * automatically wins. If it's possible for both, {@link OverloadedNumberUtil#getArgumentConversionPrice} will
+     * automatically wins. If it's possible for both, {@link OverloadedNumberUtils#getArgumentConversionPrice} will
      * be used to calculate the conversion "price", and the parameter type with lowest price wins. There are also
      * a twist with array-to-list and list-to-array conversions; we try to avoid those, so the parameter where such
      * conversion isn't needed will always win.
@@ -190,10 +190,10 @@ final class ArgumentTypes {
                 final boolean argIsNum = Number.class.isAssignableFrom(argType);
                 
                 final int numConvPrice1;
-                if (argIsNum && _ClassUtil.isNumerical(paramType1)) {
+                if (argIsNum && _ClassUtils.isNumerical(paramType1)) {
                     final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
-                            ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
-                    numConvPrice1 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType1);
+                            ? _ClassUtils.primitiveClassToBoxingClass(paramType1) : paramType1;
+                    numConvPrice1 = OverloadedNumberUtils.getArgumentConversionPrice(argType, nonPrimParamType1);
                 } else {
                     numConvPrice1 = Integer.MAX_VALUE;
                 }
@@ -202,10 +202,10 @@ final class ArgumentTypes {
                 // - FM doesn't know some of the numerical types, or the conversion between them is not allowed    
                 
                 final int numConvPrice2;
-                if (argIsNum && _ClassUtil.isNumerical(paramType2)) {
+                if (argIsNum && _ClassUtils.isNumerical(paramType2)) {
                     final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
-                            ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
-                    numConvPrice2 = OverloadedNumberUtil.getArgumentConversionPrice(argType, nonPrimParamType2);
+                            ? _ClassUtils.primitiveClassToBoxingClass(paramType2) : paramType2;
+                    numConvPrice2 = OverloadedNumberUtils.getArgumentConversionPrice(argType, nonPrimParamType2);
                 } else {
                     numConvPrice2 = Integer.MAX_VALUE;
                 }
@@ -300,16 +300,16 @@ final class ArgumentTypes {
                     if (numConvPrice1 != numConvPrice2) {
                         if (numConvPrice1 < numConvPrice2) {
                             winerParam = 1;
-                            if (numConvPrice1 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
-                                    && numConvPrice2 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
+                            if (numConvPrice1 < OverloadedNumberUtils.BIG_MANTISSA_LOSS_PRICE
+                                    && numConvPrice2 > OverloadedNumberUtils.BIG_MANTISSA_LOSS_PRICE) {
                                 paramList1StrongWinCnt++;
                             } else {
                                 paramList1WinCnt++;
                             }
                         } else {
                             winerParam = -1;
-                            if (numConvPrice2 < OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE
-                                    && numConvPrice1 > OverloadedNumberUtil.BIG_MANTISSA_LOSS_PRICE) {
+                            if (numConvPrice2 < OverloadedNumberUtils.BIG_MANTISSA_LOSS_PRICE
+                                    && numConvPrice1 > OverloadedNumberUtils.BIG_MANTISSA_LOSS_PRICE) {
                                 paramList2StrongWinCnt++;
                             } else {
                                 paramList2WinCnt++;
@@ -347,8 +347,8 @@ final class ArgumentTypes {
                     if (argTypesLen == paramTypes1Len - 1) {
                         Class<?> paramType1 = getParamType(paramTypes1, paramTypes1Len, argTypesLen, true);
                         Class<?> paramType2 = getParamType(paramTypes2, paramTypes2Len, argTypesLen, true);
-                        if (_ClassUtil.isNumerical(paramType1) && _ClassUtil.isNumerical(paramType2)) {
-                            int r = OverloadedNumberUtil.compareNumberTypeSpecificity(paramType1, paramType2);
+                        if (_ClassUtils.isNumerical(paramType1) && _ClassUtils.isNumerical(paramType2)) {
+                            int r = OverloadedNumberUtils.compareNumberTypeSpecificity(paramType1, paramType2);
                             if (r != 0) return r;
                             // falls through
                         }
@@ -377,9 +377,9 @@ final class ArgumentTypes {
         // The more specific (smaller) type wins.
         
         final Class<?> nonPrimParamType1 = paramType1.isPrimitive()
-                ? _ClassUtil.primitiveClassToBoxingClass(paramType1) : paramType1;
+                ? _ClassUtils.primitiveClassToBoxingClass(paramType1) : paramType1;
         final Class<?> nonPrimParamType2 = paramType2.isPrimitive()
-                ? _ClassUtil.primitiveClassToBoxingClass(paramType2) : paramType2;
+                ? _ClassUtils.primitiveClassToBoxingClass(paramType2) : paramType2;
                 
         if (nonPrimParamType1 == nonPrimParamType2) {
             if (nonPrimParamType1 != paramType1) {
@@ -505,7 +505,7 @@ final class ArgumentTypes {
                     return CONVERSION_DIFFICULTY_IMPOSSIBLE;
                 }
                 
-                formalNP = _ClassUtil.primitiveClassToBoxingClass(formal);
+                formalNP = _ClassUtils.primitiveClassToBoxingClass(formal);
                 if (actual == formalNP) {
                     // Character and char, etc.
                     return CONVERSION_DIFFICULTY_REFLECTION;
@@ -518,7 +518,7 @@ final class ArgumentTypes {
                 formalNP = formal;
             }
             if (Number.class.isAssignableFrom(actual) && Number.class.isAssignableFrom(formalNP)) {
-                return OverloadedNumberUtil.getArgumentConversionPrice(actual, formalNP) == Integer.MAX_VALUE
+                return OverloadedNumberUtils.getArgumentConversionPrice(actual, formalNP) == Integer.MAX_VALUE
                         ? CONVERSION_DIFFICULTY_IMPOSSIBLE : CONVERSION_DIFFICULTY_REFLECTION;
             } else if (formal.isArray()) {
                 // DefaultObjectWrapper method/constructor calls convert from List to array automatically

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
index 33d5e86..4320251 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
@@ -45,7 +45,7 @@ import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
-import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.util._StringUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -164,7 +164,7 @@ public class BeanModel
 
     private void logNoSuchKey(String key, Map<?, ?> keyMap) {
         if (LOG.isDebugEnabled()) {
-            LOG.debug("Key " + _StringUtil.jQuoteNoXSS(key) + " was not found on instance of " + 
+            LOG.debug("Key " + _StringUtils.jQuoteNoXSS(key) + " was not found on instance of " +
                 object.getClass().getName() + ". Introspection information for " +
                 "the class is: " + keyMap);
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
index 3fd3a2d..c866d65 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassBasedModelFactory.java
@@ -27,7 +27,7 @@ import java.util.concurrent.ConcurrentHashMap;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.util._ClassUtil;
+import org.apache.freemarker.core.util._ClassUtils;
 
 /**
  * Base class for hash models keyed by Java class names. 
@@ -91,7 +91,7 @@ abstract class ClassBasedModelFactory implements TemplateHashModel {
             classIntrospectorClearingCounter = classIntrospector.getClearingCounter();
         }
         try {
-            final Class clazz = _ClassUtil.forName(key);
+            final Class clazz = _ClassUtils.forName(key);
             
             // This is called so that we trigger the
             // class-reloading detector. If clazz is a reloaded class,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ebb39b84/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index ead6c7a..a1f5935 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -411,7 +411,7 @@ class ClassIntrospector {
                 Class<?>[] paramTypes = method.getParameterTypes();
                 if (paramTypes.length == 0
                         || paramTypes.length == 1 && paramTypes[0] == int.class /* indexed property reader */) {
-                    String propName = _MethodUtil.getBeanPropertyNameFromReaderMethodName(
+                    String propName = _MethodUtils.getBeanPropertyNameFromReaderMethodName(
                             method.getName(), method.getReturnType());
                     if (propName != null) {
                         if (mergedPRMPs == null) {