freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [1/2] incubator-freemarker git commit: FREEMARKER-65: Utilities for argument validation: work in progress... so far only some internal cleanup.
Date Tue, 15 Aug 2017 23:08:40 GMT
Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 78428211c -> e941aedc1


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java
index aef918d..10401fd 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/UnexpectedTypeException.java
@@ -19,8 +19,7 @@
 
 package org.apache.freemarker.core;
 
-import java.io.Serializable;
-
+import org.apache.freemarker.core.model.TemplateCallableModel;
 import org.apache.freemarker.core.model.TemplateModel;
 
 /**
@@ -39,7 +38,11 @@ public class UnexpectedTypeException extends TemplateException {
     UnexpectedTypeException(
             ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, Environment env)
             throws InvalidReferenceException {
-        super(null, env, blamed, newDescriptionBuilder(blamed, null, null, model, expectedTypesDesc, expectedTypes,
+        super(null, env, blamed, newDescriptionBuilder(
+                blamed,
+                null,
+                null, -1,
+                model, expectedTypesDesc, expectedTypes,
                 env));
     }
 
@@ -47,7 +50,11 @@ public class UnexpectedTypeException extends TemplateException {
             ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, String tip,
             Environment env)
             throws InvalidReferenceException {
-        super(null, env, blamed, newDescriptionBuilder(blamed, null, null, model, expectedTypesDesc, expectedTypes,
+        super(null, env, blamed, newDescriptionBuilder(
+                blamed,
+                null,
+                null, -1,
+                model, expectedTypesDesc, expectedTypes,
                 env)
                 .tip(tip));
     }
@@ -56,7 +63,11 @@ public class UnexpectedTypeException extends TemplateException {
             ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, Object[] tips,
             Environment env)
             throws InvalidReferenceException {
-        super(null, env, blamed, newDescriptionBuilder(blamed, null, null, model, expectedTypesDesc, expectedTypes, env)
+        super(null, env, blamed, newDescriptionBuilder(
+                blamed,
+                null,
+                null, -1,
+                model, expectedTypesDesc, expectedTypes, env)
                 .tips(tips));
     }
 
@@ -69,7 +80,10 @@ public class UnexpectedTypeException extends TemplateException {
             Environment env)
             throws InvalidReferenceException {
         super(null, env, null, newDescriptionBuilder(
-                null, blamedAssignmentTargetVarName, null, model, expectedTypesDesc, expectedTypes, env).tips(tips));
+                null,
+                blamedAssignmentTargetVarName,
+                null, -1,
+                model, expectedTypesDesc, expectedTypes, env).tips(tips));
     }
 
     /**
@@ -77,26 +91,40 @@ public class UnexpectedTypeException extends TemplateException {
      * expects.
      */
     UnexpectedTypeException(
-            Serializable blamedArgumentNameOrIndex, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes,
+            TemplateCallableModel callableModel, int argArrayIndex,
+            TemplateModel model, String expectedTypesDesc, Class[] expectedTypes,
             Object[] tips,
             Environment env)
             throws InvalidReferenceException {
         super(null, env, null, newDescriptionBuilder(
-                null, null, blamedArgumentNameOrIndex, model, expectedTypesDesc, expectedTypes, env).tips(tips));
+                null,
+                null,
+                callableModel, argArrayIndex,
+                model,
+                expectedTypesDesc, expectedTypes, env).tips(tips));
     }
 
     private static _ErrorDescriptionBuilder newDescriptionBuilder(
-            ASTExpression blamed, String blamedAssignmentTargetVarName, Serializable blamedArgumentNameOrIndex,
-            TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, Environment env)
+            ASTExpression blamed, String blamedAssignmentTargetVarName,
+            TemplateCallableModel callableModel, int argArrayIndex,
+            TemplateModel model, String expectedTypesDesc, Class<? extends TemplateModel>[] expectedTypes, Environment env)
             throws InvalidReferenceException {
         if (model == null) {
             throw InvalidReferenceException.getInstance(blamed, env);
         }
 
         _ErrorDescriptionBuilder errorDescBuilder = new _ErrorDescriptionBuilder(
-                unexpectedTypeErrorDescription(expectedTypesDesc,
-                        blamed, blamedAssignmentTargetVarName, blamedArgumentNameOrIndex,
-                        model))
+                callableModel == null
+                        ? unexpectedTypeErrorDescription(
+                            expectedTypesDesc,
+                            blamed,
+                            blamedAssignmentTargetVarName,
+                            model)
+                        : unexpectedTypeErrorDescription(
+                                expectedTypesDesc,
+                                blamed,
+                                callableModel, argArrayIndex,
+                                model))
                 .blame(blamed).showBlamer(true);
         if (model instanceof _UnexpectedTypeErrorExplainerTemplateModel) {
             Object[] tip = ((_UnexpectedTypeErrorExplainerTemplateModel) model).explainTypeError(expectedTypes);
@@ -109,7 +137,8 @@ public class UnexpectedTypeException extends TemplateException {
 
     private static Object[] unexpectedTypeErrorDescription(
             String expectedTypesDesc,
-            ASTExpression blamed, String blamedAssignmentTargetVarName, Serializable blamedArgumentNameOrIndex,
+            ASTExpression blamed,
+            String blamedAssignmentTargetVarName,
             TemplateModel model) {
         return new Object[] {
                 "Expected ", new _DelayedAOrAn(expectedTypesDesc), ", but ", (
@@ -117,20 +146,26 @@ public class UnexpectedTypeException extends TemplateException {
                                 ? new Object[] {
                                         "assignment target variable ",
                                         new _DelayedJQuote(blamedAssignmentTargetVarName) }
-                        : blamedArgumentNameOrIndex != null
-                                ? new Object[] {
-                                        "the ",
-                                        (blamedArgumentNameOrIndex instanceof Integer
-                                                ? new _DelayedOrdinal(((Integer) blamedArgumentNameOrIndex) + 1)
-                                                : new _DelayedJQuote(blamedArgumentNameOrIndex)),
-                                        " argument"}
                         : blamed != null
                                 ? "this"
                         : "the expression"
                 ),
                 " has evaluated to ",
                 new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(model)),
-                (blamedAssignmentTargetVarName == null ? ":" : ".")};
+                (blamed != null ? ":" : ".")};
     }
-    
+
+    private static Object[] unexpectedTypeErrorDescription(
+            String expectedTypesDesc,
+            ASTExpression blamed,
+            TemplateCallableModel callableModel, int argArrayIndex,
+            TemplateModel actualValue) {
+        // TODO
+        return new Object[]{
+                blamed, " expects ", new _DelayedAOrAn(expectedTypesDesc), " as its ", argArrayIndex, " arg, but it "
+                + "was " + new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(actualValue)),
+                (blamed != null ? ":" : ".")
+        };
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
index ecef475..0f11a18 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CallableUtils.java
@@ -19,23 +19,26 @@
 
 package org.apache.freemarker.core;
 
+import static org.apache.freemarker.core.util.TemplateLanguageUtils.*;
+
 import java.io.IOException;
-import java.io.Serializable;
 import java.io.Writer;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.List;
 
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateCallableModel;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelWithOriginName;
 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.util.TemplateLanguageUtils;
 import org.apache.freemarker.core.util.StringToIndexMap;
+import org.apache.freemarker.core.util.TemplateLanguageUtils;
 import org.apache.freemarker.core.util._CollectionUtils;
 
 /**
@@ -51,6 +54,210 @@ public final class _CallableUtils {
         //
     }
 
+    /** Convenience method for calling {@link #newGenericExecuteException(TemplateCallableModel, boolean, String)}. */
+    public static TemplateException newGenericExecuteException(
+            TemplateFunctionModel callable, String errorDescription) {
+        return newGenericExecuteException(callable, true, errorDescription);
+    }
+
+    /** Convenience method for calling {@link #newGenericExecuteException(TemplateCallableModel, boolean, String)}. */
+    public static TemplateException newGenericExecuteException(
+            TemplateDirectiveModel callable, String errorDescription) {
+        return newGenericExecuteException(callable, false, errorDescription);
+    }
+
+    /**
+     * @param errorDescription Complete sentence describing the problem. This will be after
+     *      {@code "When calling xxx: "}.
+     */
+    public static TemplateException newGenericExecuteException(
+            TemplateCallableModel callable, boolean calledAsFunction, String errorDescription) {
+        return new TemplateException(
+                getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                errorDescription);
+    }
+
+    public static TemplateException newArgumentValueException(
+            int argIdx, String problemDescription,
+            TemplateDirectiveModel callable) {
+        return newArgumentValueException(
+                argIdx, problemDescription, callable, false);
+    }
+
+    public static TemplateException newArgumentValueException(
+            int argIdx, String problemDescription,
+            TemplateFunctionModel callable) {
+        return newArgumentValueException(
+                argIdx, problemDescription, callable, true);
+    }
+
+    /**
+     * @param problemDescription The continuation of a sentence like {@code "When calling xxx: The 1st argument "}, for
+     *                           example {@code "must be a positive number."}.
+     */
+    public static TemplateException newArgumentValueException(
+            int argIdx, String problemDescription,
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return new TemplateException(
+                getMessageArgumentProblem(
+                        callable, argIdx,
+                        problemDescription,
+                        calledAsFunction));
+    }
+
+    /**
+     * Convenience method to call
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
+            TemplateDirectiveModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedType,
+                callable, false);
+    }
+
+    /**
+     * Convenience method to call
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
+            TemplateFunctionModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedType,
+                callable, true);
+    }
+
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel> expectedType,
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return new TemplateException(
+                getMessageBadArgumentType(argValue, argIdx,
+                        new Class[] { expectedType },
+                        TemplateLanguageUtils.getTypeName(expectedType),
+                        callable, calledAsFunction));
+    }
+
+    /**
+     * Convenience method for calling
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class[], String, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
+            TemplateDirectiveModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedTypes, expectedTypeDescription,
+                callable, false);
+    }
+
+    /**
+     * Convenience method for calling
+     * {@link #newArgumentValueTypeException(TemplateModel, int, Class[], String, TemplateCallableModel, boolean)}.
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
+            TemplateFunctionModel callable) {
+        return newArgumentValueTypeException(
+                argValue, argIdx, expectedTypes, expectedTypeDescription,
+                callable, true);
+    }
+
+    /**
+     * @param expectedTypeDescription Something like "string or number".
+     */
+    public static TemplateException newArgumentValueTypeException(
+            TemplateModel argValue, int argIdx, Class[] expectedTypes, String expectedTypeDescription,
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return new TemplateException(
+                getMessageBadArgumentType(argValue, argIdx,
+                        expectedTypes,
+                        expectedTypeDescription,
+                        callable, calledAsFunction));
+    }
+
+    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateFunctionModel callable) {
+        return newNullOrOmittedArgumentException(argIdx, callable, true);
+    }
+
+    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateDirectiveModel callable) {
+        return newNullOrOmittedArgumentException(argIdx, callable, false);
+    }
+
+    public static TemplateException newNullOrOmittedArgumentException(int argIdx, TemplateCallableModel callable,
+            boolean calledAsFunction) {
+        return newArgumentValueException(argIdx, "can't be omitted or null.", callable, calledAsFunction);
+    }
+
+    /**
+     * Something like {@code "When calling function \"lib.ftl:foo\": " or "When calling ?leftPad: "}
+     */
+    private static Object getMessagePartWhenCallingSomethingColon(
+            TemplateCallableModel callable, boolean calledAsFunction) {
+        return callable instanceof ASTExpBuiltIn.BuiltInCallable
+                ? new Object[] { "When calling ?", ((ASTExpBuiltIn.BuiltInCallable) callable).getBuiltInName() + ": " }
+                : new Object[] {
+                        "When calling ",
+                        getCallableTypeName(callable, calledAsFunction),
+                        " ",
+                        callable instanceof TemplateModelWithOriginName
+                                ? new _DelayedJQuote(((TemplateModelWithOriginName) callable).getOriginName())
+                                : new _DelayedShortClassName(callable.getClass()),
+                        ": "
+                };
+    }
+
+    private static Object getMessagePartsTheSomethingArgument(ArgumentArrayLayout argsLayout, int argsArrayIndex) {
+        if (argsArrayIndex < 0) {
+            throw new IllegalArgumentException("argsArrayIndex can't be negative");
+        }
+        if (argsLayout == null || argsArrayIndex < argsLayout.getPredefinedPositionalArgumentCount()) {
+            return new Object[] { "The ", new _DelayedOrdinal(argsArrayIndex + 1), " argument " };
+        } else if (argsLayout.getPositionalVarargsArgumentIndex() == argsArrayIndex) {
+            return argsLayout.getNamedVarargsArgumentIndex() != -1 ? "The positional varargs argument "
+                    : "The varargs argument ";
+        } else if (argsLayout.getNamedVarargsArgumentIndex() == argsArrayIndex) {
+            return "The named varargs argument ";
+        } else {
+            String argName = argsLayout.getPredefinedNamedArgumentsMap().getKeyOfValue(argsArrayIndex);
+                return argName != null
+                        ? new Object[] { "The ", new _DelayedJQuote(argName), " argument " }
+                        : "The argument "; // Shouldn't occur...
+        }
+    }
+
+    static Object[] getMessageArgumentProblem(TemplateCallableModel callable, int argIndex, Object
+            problemDescription, boolean calledAsFunction) {
+        return new Object[] {
+                getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                getMessagePartsTheSomethingArgument(
+                        calledAsFunction ? ((TemplateFunctionModel) callable).getFunctionArgumentArrayLayout()
+                                : ((TemplateDirectiveModel) callable).getDirectiveArgumentArrayLayout(),
+                        argIndex),
+                problemDescription
+        };
+    }
+
+    private static _ErrorDescriptionBuilder getMessageBadArgumentType(
+            TemplateModel argValue, int argIdx, Class<? extends TemplateModel>[] expectedTypes,
+            String expectedTypesDesc, TemplateCallableModel callable,
+            boolean calledAsFunction) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                getMessageArgumentProblem(
+                        callable, argIdx,
+                        new Object[]{ " should be ", new _DelayedAOrAn(expectedTypesDesc), ", but was ",
+                                new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(argValue)),
+                                "." },
+                        calledAsFunction));
+        if (argValue instanceof _UnexpectedTypeErrorExplainerTemplateModel) {
+            Object[] tip = ((_UnexpectedTypeErrorExplainerTemplateModel) argValue).explainTypeError(expectedTypes);
+            if (tip != null) {
+                desc.tip(tip);
+            }
+        }
+        return desc;
+    }
+
     public static void executeWith0Arguments(
             TemplateDirectiveModel directive, CallPlace callPlace, Writer out, Environment env)
             throws IOException, TemplateException {
@@ -86,109 +293,231 @@ public final class _CallableUtils {
         }
     }
 
-    public static Number castArgToNumber(TemplateModel[] args, int argIndex) throws TemplateException {
-        return castArgToNumber(args, argIndex, false);
-    }
+    // String arg:
 
-    public static Number castArgToNumber(TemplateModel[] args, int argIndex, boolean optional)
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, true, false)}.
+     */
+    public static String getStringArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
             throws TemplateException {
-        return castArgToNumber(args[argIndex], argIndex, optional);
+        return castArgumentValueToString(args[argIndex], argIndex, callable, true, false);
     }
 
-    public static Number castArgToNumber(TemplateModel argValue, int argIndex)
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, false, false)}.
+     */
+    public static String getStringArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
             throws TemplateException {
-        return castArgToNumber(argValue, argIndex, false);
+        return castArgumentValueToString(args[argIndex], argIndex, callable, false, false);
     }
 
-    public static Number castArgToNumber(TemplateModel argValue, int argIndex, boolean optional)
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, true, true)}.
+     */
+    public static String getOptionalStringArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
             throws TemplateException {
-        return castArgToNumber(argValue, null, argIndex, optional);
+        return castArgumentValueToString(args[argIndex], argIndex, callable, true, true);
     }
 
-    public static Number castArgToNumber(TemplateModel argValue, String argName, boolean optional)
+    /**
+     * Convenience method to call
+     * {@link #castArgumentValueToString(TemplateModel, int, TemplateCallableModel, boolean, boolean)
+     * castArgumentValueToString(args[argIndex], argIndex, callable, false, true)}.
+     */
+    public static String getOptionalStringArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
             throws TemplateException {
-        return castArgToNumber(argValue, argName, -1, optional);
+        return castArgumentValueToString(args[argIndex], argIndex, callable, false, true);
     }
 
-    private static Number castArgToNumber(TemplateModel argValue, String argName, int argIndex, boolean optional)
+    /**
+     * Checks if the argument value is a string; it does NOT check if {@code args} is big enough.
+     *
+     * @param calledAsFunction
+     *         If {@code callable} was called as function (as opposed to called as a directive)
+     * @param optional
+     *         If we allow a {@code null} return value
+     *
+     * @return Null {@code null} if the argument was omitted or {@code null}
+     *
+     * @throws TemplateException
+     *         If the argument is not of the proper type or is non-optional yet {@code null}. The error message
+     *         describes the problem in detail.
+     */
+    public static String castArgumentValueToString(
+            TemplateModel argValue, int argIdx, TemplateCallableModel callable,
+            boolean calledAsFunction, boolean optional)
             throws TemplateException {
-        if (argValue instanceof TemplateNumberModel) {
-            return ((TemplateNumberModel) argValue).getAsNumber();
+        if (argValue instanceof TemplateScalarModel) {
+            return _EvalUtils.modelToString((TemplateScalarModel) argValue, null);
         }
         if (argValue == null) {
             if (optional) {
                 return null;
             }
-            throw new TemplateException(
-                    "The ", argName != null ? new _DelayedJQuote(argName) : new _DelayedOrdinal(argIndex + 1),
-                    " argument can't be null.");
+            throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction);
         }
-        throw new NonNumericalException((Serializable) argName != null ? argName : argIndex, argValue, null, null);
+        throw newArgumentValueTypeException(argValue, argIdx, TemplateScalarModel.class, callable, calledAsFunction);
     }
+
+    // Number arg:
     
-    //
+    public static Number getNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, true, false);
+    }
 
-    public static String castArgToString(List<? extends TemplateModel> args, int argIndex) throws TemplateException {
-        return castArgToString(args, argIndex, false);
+    public static Number getNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, false);
     }
 
-    public static String castArgToString(List<? extends TemplateModel> args, int argIndex, boolean optional) throws
-            TemplateException {
-        return castArgToString(args.get(argIndex), argIndex, optional);
+    public static Number getOptionalNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, true, true);
+    }
+
+    public static Number getOptionalNumberArgument(
+            TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
+            throws TemplateException {
+        return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, true);
     }
 
-    public static String castArgToString(TemplateModel[] args, int argIndex) throws TemplateException {
-        return castArgToString(args, argIndex, false);
+    public static Number castArgumentValueToNumber(
+            TemplateModel argValue, int argIdx, TemplateCallableModel callable,
+            boolean calledAsFunction, boolean optional)
+            throws TemplateException {
+        if (argValue instanceof TemplateNumberModel) {
+            return _EvalUtils.modelToNumber((TemplateNumberModel) argValue, null);
+        }
+        if (argValue == null) {
+            if (optional) {
+                return null;
+            }
+            throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction);
+        }
+        throw newArgumentValueTypeException(
+                argValue, argIdx, TemplateNumberModel.class, callable,
+                calledAsFunction);
     }
 
-    public static String castArgToString(TemplateModel[] args, int argIndex, boolean optional) throws TemplateException {
-        return castArgToString(args[argIndex], argIndex, optional);
+    // TODO boolean, etc.
+
+    // Argument count
+
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int expectedCnt, TemplateFunctionModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, expectedCnt, callable, true);
     }
 
-    public static String castArgToString(TemplateModel argValue, int argIndex) throws TemplateException {
-        return castArgToString(argValue, argIndex, false);
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int expectedCnt, TemplateDirectiveModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, expectedCnt, callable, false);
     }
 
-    public static String castArgToString(TemplateModel argValue, int argIndex, boolean optional) throws TemplateException {
-        return castArgToString(argValue, null, argIndex, optional);
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int expectedCnt,
+            TemplateCallableModel callable, boolean calledAsFunction) throws TemplateException {
+        checkArgumentCount(argCnt, expectedCnt, expectedCnt, callable, calledAsFunction);
     }
 
-    public static String castArgToString(TemplateModel argValue, String argName, boolean optional) throws TemplateException {
-        return castArgToString(argValue, argName, -1, optional);
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt, TemplateFunctionModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, minCnt, maxCnt, callable, true);
     }
 
-    private static String castArgToString(
-            TemplateModel argValue, String argName, int argIndex,
-            boolean optional) throws TemplateException {
-        if (argValue instanceof TemplateScalarModel) {
-            return _EvalUtils.modelToString((TemplateScalarModel) argValue, null, null);
+    /** Convenience method for calling {@link #checkArgumentCount(int, int, int, TemplateCallableModel, boolean)}. */
+    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt, TemplateDirectiveModel callable)
+            throws TemplateException {
+        checkArgumentCount(argCnt, minCnt, maxCnt, callable, false);
+    }
+
+    /**
+     * Useful when the {@link ArgumentArrayLayout} is {@code null} and so the argument array length is not fixed,
+     * to check if the number of arguments is in the given range.
+     */
+    public static void checkArgumentCount(int argCnt, int minCnt, int maxCnt,
+        TemplateCallableModel callable, boolean calledAsFunction) throws TemplateException {
+        if (argCnt < minCnt || argCnt > maxCnt) {
+            throw new TemplateException(
+                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                    getMessagePartExpectedNArgumentButHadM(argCnt, minCnt, maxCnt));
         }
-        if (argValue == null) {
-            if (optional) {
-                return null;
+    }
+
+    private static Object[] getMessagePartExpectedNArgumentButHadM(int argCnt, int minCnt, int maxCnt) {
+        ArrayList<Object> desc = new ArrayList<>(20);
+
+        desc.add("Expected ");
+
+        if (minCnt == maxCnt) {
+            if (maxCnt == 0) {
+                desc.add("no");
+            } else {
+                desc.add(maxCnt);
+            }
+        } else if (maxCnt - minCnt == 1) {
+            desc.add(minCnt);
+            desc.add(" or ");
+            desc.add(maxCnt);
+        } else {
+            desc.add(minCnt);
+            if (maxCnt != Integer.MAX_VALUE) {
+                desc.add(" to ");
+                desc.add(maxCnt);
+            } else {
+                desc.add(" or more (unlimited)");
             }
-            throw new TemplateException(
-                    "The ", argName != null ? new _DelayedJQuote(argName) : new _DelayedOrdinal(argIndex + 1),
-                    " argument can't be null.");
         }
-        throw new NonStringException((Serializable) argName != null ? argName : argIndex, argValue, null, null);
+        desc.add(" argument");
+        if (maxCnt > 1) desc.add("s");
+
+        desc.add(" but has received ");
+        if (argCnt == 0) {
+            desc.add("none");
+        } else {
+            desc.add(argCnt);
+        }
+        desc.add(".");
+
+        return desc.toArray();
     }
 
+    //
+
     static TemplateModel[] getExecuteArgs(
             ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout,
-            TemplateCallableModel callableValue,
+            TemplateCallableModel callable, boolean calledAsFunction,
             Environment env) throws
             TemplateException {
         return argsLayout != null
-                ? getExecuteArgsBasedOnLayout(positionalArgs, namedArgs, argsLayout, callableValue, env)
-                : getExecuteArgsWithoutLayout(positionalArgs, namedArgs, callableValue, env);
+                ? getExecuteArgsBasedOnLayout(positionalArgs, namedArgs, argsLayout, callable, calledAsFunction, env)
+                : getExecuteArgsWithoutLayout(positionalArgs, namedArgs, callable, calledAsFunction, env);
     }
 
-    private static TemplateModel[] getExecuteArgsWithoutLayout(ASTExpression[] positionalArgs,
-            NamedArgument[] namedArgs, TemplateCallableModel callableValue, Environment env)
+    private static TemplateModel[] getExecuteArgsWithoutLayout(
+            ASTExpression[] positionalArgs, NamedArgument[] namedArgs,
+            TemplateCallableModel callable, boolean calledAsFunction,
+            Environment env)
             throws TemplateException {
         if (namedArgs != null) {
-            throw new TemplateException(env, getNamedArgumentsNotSupportedMessage(callableValue, namedArgs[0]));
+            throw new TemplateException(env,
+                    getNamedArgumentsNotSupportedMessage(callable, namedArgs[0], calledAsFunction));
         }
 
         TemplateModel[] execArgs;
@@ -206,7 +535,7 @@ public final class _CallableUtils {
 
     private static TemplateModel[] getExecuteArgsBasedOnLayout(
             ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout,
-            TemplateCallableModel callableValue,
+            TemplateCallableModel callable, boolean calledAsFunction,
             Environment env) throws TemplateException {
         int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount();
         int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex();
@@ -235,10 +564,12 @@ public final class _CallableUtils {
             }
             execArgs[posVarargsArgIdx] = varargsSeq;
         } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) {
-            checkSupportsAnyParameters(callableValue, argsLayout, env);
+            checkSupportsAnyParameters(callable, argsLayout, calledAsFunction);
             List<String> validPredefNames = argsLayout.getPredefinedNamedArgumentsMap().getKeys();
             _ErrorDescriptionBuilder errorDesc = new _ErrorDescriptionBuilder(
-                    "The called ", TemplateLanguageUtils.getCallableTypeName(callableValue), " ",
+                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                    "This ", getCallableTypeName(callable, calledAsFunction),
+                    " ",
                     (predefPosArgCnt != 0
                             ? new Object[]{ "can only have ", predefPosArgCnt }
                             : "can't have"
@@ -248,25 +579,23 @@ public final class _CallableUtils {
                     (!argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported() ?
                             new Object[] {
                                     " Try to pass arguments by name (as in ",
-                                    (callableValue instanceof TemplateDirectiveModel
+                                    (callable instanceof TemplateDirectiveModel
                                             ? "<@example x=1 y=2 />"
                                             : "example(x=1, y=2)"),
                                     ")",
                                     (!validPredefNames.isEmpty()
-                                            ? new Object[] { " The supported parameter names are:\n",
+                                            ? new Object[] { " The supported parameter names are: ",
                                             new _DelayedJQuotedListing(validPredefNames)}
                                             : _CollectionUtils.EMPTY_OBJECT_ARRAY)}
                             : "")
             );
-            if (callableValue instanceof Environment.TemplateLanguageDirective
+            if (callable instanceof Environment.TemplateLanguageDirective
                     && !argsLayout.isPositionalParametersSupported() && argsLayout.isNamedParametersSupported()) {
                 errorDesc.tip("You can pass a parameter by position (i.e., without specifying its name, as you"
                         + " have tried now) when the macro has defined that parameter to be a positional parameter. "
                         + "See in the documentation how, and when that's a good practice.");
             }
-            throw new TemplateException(env,
-                    errorDesc
-            );
+            throw new TemplateException(env, errorDesc);
         }
 
         int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
@@ -280,13 +609,15 @@ public final class _CallableUtils {
                 } else {
                     if (namedVarargsHash == null) {
                         if (namedVarargsArgumentIndex == -1) {
-                            checkSupportsAnyParameters(callableValue, argsLayout, env);
+                            checkSupportsAnyParameters(callable, argsLayout, calledAsFunction);
                             Collection<String> validNames = predefNamedArgsMap.getKeys();
                             throw new TemplateException(env,
                                     validNames == null || validNames.isEmpty()
-                                            ? getNamedArgumentsNotSupportedMessage(callableValue, namedArg)
+                                            ? getNamedArgumentsNotSupportedMessage(
+                                                    callable, namedArg, calledAsFunction)
                                             : new Object[] {
-                                                    "The called ", TemplateLanguageUtils.getCallableTypeName(callableValue),
+                                                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                                                    "This ", getCallableTypeName(callable, calledAsFunction),
                                                     " has no parameter that's passed by name and is called ",
                                                     new _DelayedJQuote(namedArg.name),
                                                     ". The supported parameter names are:\n",
@@ -306,14 +637,15 @@ public final class _CallableUtils {
         return execArgs;
     }
 
-    private static Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callableValue,
-            NamedArgument namedArg) {
+    private static Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callable,
+            NamedArgument namedArg, boolean calledAsFunction) {
         return new Object[] {
-                "The called ", TemplateLanguageUtils.getCallableTypeName(callableValue),
+                getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                "This ", getCallableTypeName(callable, calledAsFunction),
                 " can't have arguments that are passed by name (like ",
                 new _DelayedJQuote(namedArg.name), "). Try to pass arguments by position "
                 + "(i.e, without name, as in ",
-                (callableValue instanceof TemplateDirectiveModel
+                (callable instanceof TemplateDirectiveModel
                         ? "<@example arg1, arg2, arg3 />"
                         : "example(arg1, arg2, arg3)"),
                 ")."
@@ -321,11 +653,13 @@ public final class _CallableUtils {
     }
 
     static private void checkSupportsAnyParameters(
-            TemplateCallableModel callableValue, ArgumentArrayLayout argsLayout, Environment env)
+            TemplateCallableModel callable, ArgumentArrayLayout argsLayout, boolean calledAsFunction)
             throws TemplateException {
         if (argsLayout.getTotalLength() == 0) {
-            throw new TemplateException(env,
-                    "The called ", TemplateLanguageUtils.getCallableTypeName(callableValue), " doesn't support any parameters.");
+            throw new TemplateException(
+                    getMessagePartWhenCallingSomethingColon(callable, calledAsFunction),
+                    "This ", getCallableTypeName(callable, calledAsFunction),
+                    " doesn't support any parameters.");
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/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
index e187f32..e9a7967 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
@@ -56,9 +56,8 @@ public class _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)
+    static String modelToString(TemplateScalarModel model, ASTExpression expr)
     throws TemplateModelException {
         String value = model.getAsString();
         if (value == null) {
@@ -265,8 +264,8 @@ public class _EvalUtils {
                 throw new TemplateException(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);
+            String leftString = _EvalUtils.modelToString((TemplateScalarModel) leftValue, leftExp);
+            String rightString = _EvalUtils.modelToString((TemplateScalarModel) rightValue, rightExp);
             // 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) {
@@ -442,7 +441,7 @@ public class _EvalUtils {
             Environment env)
             throws TemplateException {
         if (tm instanceof TemplateScalarModel) {
-            return modelToString((TemplateScalarModel) tm, exp, env);
+            return modelToString((TemplateScalarModel) tm, exp);
         } else if (tm == null) {
             if (exp != null) {
                 throw InvalidReferenceException.getInstance(exp, env);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
index b3807f2..6980c82 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
@@ -51,7 +51,7 @@ public interface TemplateHashModelEx2 extends TemplateHashModelEx {
     }
     
     /**
-     * Iterates over the key-value pairs in a hash. This is very similar to an {@link Iterator}, but has a oms item
+     * Iterates over the key-value pairs in a hash. This is very similar to an {@link Iterator}, but has a fixed item
      * type, can throw {@link TemplateModelException}-s, and has no {@code remove()} method. 
      */
     interface KeyValuePairIterator {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateModelWithOriginName.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateModelWithOriginName.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateModelWithOriginName.java
new file mode 100644
index 0000000..087cb07
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateModelWithOriginName.java
@@ -0,0 +1,39 @@
+/*
+ * 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.model;
+
+import org.apache.freemarker.core.model.impl.JavaMethodModel;
+
+/**
+ * For some values it makes sense to carry information about where the value is originating from, and this information
+ * can be used in error messages and other kind of debugging information.
+ */
+public interface TemplateModelWithOriginName extends TemplateModel {
+
+    /**
+     * Returns some kind of symbolical name that identifies where the value is originating from. For example, for a
+     * {@link JavaMethodModel} it will be like {@code "com.example.somepackage.SomeClass.someMethod"}. This is not
+     * intended for machine interpretation, and in that sense it's just an informal name. It should be take into
+     * consideration that the this value will be possibly shown in error messages quoted in Java-style (so special
+     * character will be escaped).
+     */
+    String getOriginName();
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/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 77d1790..cf05a09 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
@@ -357,7 +357,7 @@ final class ArgumentTypes {
                         return 0;
                     }
                 } else {
-                    // The method with more oms parameters wins:
+                    // The method with more fixed parameters wins:
                     return paramTypes1Len - paramTypes2Len;
                 }
             } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
index 6fb04c6..da73ef5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/JavaMethodModel.java
@@ -25,12 +25,13 @@ import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelWithOriginName;
 
 /**
  * Common super interface (marker interface) for {@link TemplateFunctionModel}-s that stand for Java methods; do not
  * implement it yourself! It meant to be implemented inside FreeMarker only.
  */
-public interface JavaMethodModel extends TemplateFunctionModel {
+public interface JavaMethodModel extends TemplateFunctionModel, TemplateModelWithOriginName {
 
     /**
      * Calls {@link #execute(TemplateModel[], CallPlace, Environment)}, but it emphasizes that the
@@ -49,4 +50,21 @@ public interface JavaMethodModel extends TemplateFunctionModel {
      */
     @Override
     ArgumentArrayLayout getFunctionArgumentArrayLayout();
+
+    /**
+     * The class where the method was declared. For overloaded methods, this should the most specific class where some
+     * of the overloads are declared.
+     */
+    Class<?> getMethodDeclaringClass();
+
+    /**
+     * The name of the method.
+     */
+    String getMethodName();
+
+    /**
+     * Returns package name + {@code "."} + method name.
+     */
+    @Override
+    String getOriginName();
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedJavaMethodModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedJavaMethodModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedJavaMethodModel.java
index b662390..17d82a8 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedJavaMethodModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedJavaMethodModel.java
@@ -83,4 +83,19 @@ class OverloadedJavaMethodModel implements JavaMethodModel {
         return null;
     }
 
+    @Override
+    public Class<?> getMethodDeclaringClass() {
+        return overloadedMethods.getMethodDeclaringClass();
+    }
+
+    @Override
+    public String getMethodName() {
+        return overloadedMethods.getMethodName();
+    }
+
+    @Override
+    public String getOriginName() {
+        return getMethodDeclaringClass().getName() + "." + getMethodName();
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
index 11a45bd..fc1c344 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethods.java
@@ -45,7 +45,9 @@ final class OverloadedMethods {
 
     private final OverloadedMethodsSubset fixArgMethods;
     private OverloadedMethodsSubset varargMethods;
-    
+    private String methodName;
+    private Class methodDeclaringClass;
+
     OverloadedMethods() {
         fixArgMethods = new OverloadedFixArgsMethods();
     }
@@ -53,15 +55,28 @@ final class OverloadedMethods {
     void addMethod(Method method) {
         final Class<?>[] paramTypes = method.getParameterTypes();
         addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(method, paramTypes));
+        if (methodName == null) {
+            methodName = method.getName();
+        }
+        Class<?> newMethodDeclaringClass = method.getDeclaringClass();
+        if (methodDeclaringClass == null
+                || newMethodDeclaringClass != methodDeclaringClass
+                        && methodDeclaringClass.isAssignableFrom(newMethodDeclaringClass)) {
+            methodDeclaringClass = newMethodDeclaringClass;
+        }
     }
 
     void addConstructor(Constructor<?> constr) {
         final Class<?>[] paramTypes = constr.getParameterTypes();
         addCallableMemberDescriptor(new ReflectionCallableMemberDescriptor(constr, paramTypes));
+        if (methodName == null) {
+            methodName = constr.getName();
+            methodDeclaringClass = constr.getDeclaringClass();
+        }
     }
     
     private void addCallableMemberDescriptor(ReflectionCallableMemberDescriptor memberDesc) {
-        // Note: "varargs" methods are always callable as oms args, with a sequence (array) as the last parameter.
+        // Note: "varargs" methods are always callable as fixed args, with a sequence (array) as the last parameter.
         fixArgMethods.addCallableMemberDescriptor(memberDesc);
         if (memberDesc.isVarargs()) {
             if (varargMethods == null) {
@@ -73,7 +88,7 @@ final class OverloadedMethods {
     
     MemberAndArguments getMemberAndArguments(TemplateModel[] tmArgs, DefaultObjectWrapper unwrapper)
     throws TemplateModelException {
-        // Try to find a oms args match:
+        // Try to find a fixed args match:
         MaybeEmptyMemberAndArguments fixArgsRes = fixArgMethods.getMemberAndArguments(tmArgs, unwrapper);
         if (fixArgsRes instanceof MemberAndArguments) {
             return (MemberAndArguments) fixArgsRes;
@@ -101,6 +116,14 @@ final class OverloadedMethods {
         throw new _TemplateModelException(edb);
     }
 
+    String getMethodName() {
+        return methodName;
+    }
+
+    Class getMethodDeclaringClass() {
+        return methodDeclaringClass;
+    }
+
     private Object[] toCompositeErrorMessage(
             final EmptyMemberAndArguments fixArgsEmptyRes, final EmptyMemberAndArguments varargsEmptyRes,
             TemplateModel[] tmArgs) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
index 45cf7f3..b91f9a6 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedMethodsSubset.java
@@ -61,7 +61,7 @@ abstract class OverloadedMethodsSubset {
     private int[/*number of args*/][/*arg index*/] typeFlagsByParamCount;
     
     // TODO: This can cause memory-leak when classes are re-loaded. However, first the genericClassIntrospectionCache
-    // and such need to be oms in this regard. 
+    // and such need to be fixed in this regard.
     private final Map<ArgumentTypes, MaybeEmptyCallableMemberDescriptor> argTypesToMemberDescCache
             = new ConcurrentHashMap<>(6, 0.75f, 1);
     
@@ -199,7 +199,7 @@ abstract class OverloadedMethodsSubset {
         } else if (Number.class.isAssignableFrom(c1) && Number.class.isAssignableFrom(c2)) {
             // We don't want the unwrapper to convert to a numerical super-type [*] as it's not yet known what the
             // actual number type of the chosen method will be. We will postpone the actual numerical conversion
-            // until that, especially as some conversions (like oms point to floating point) can be lossy.
+            // until that, especially as some conversions (like fixed point to floating point) can be lossy.
             // * Numerical super-type: Like long > int > short > byte.  
             return Number.class;
         } else if (c1WasPrim || c2WasPrim) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
index b4cca30..f794083 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
@@ -112,7 +112,7 @@ public class ResourceBundleModel extends BeanModel implements TemplateFunctionMo
         if (args.length < 1)
             throw new TemplateException("No message key was specified", env);
         // Read it
-        String key = _CallableUtils.castArgToString(args, 0);
+        String key = _CallableUtils.getStringArgument(args, 0, this);
         try {
             if (args.length == 1) {
                 return wrap(((ResourceBundle) object).getObject(key));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java
index 6438596..727c9e3 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleJavaMethodModel.java
@@ -119,5 +119,19 @@ public final class SimpleJavaMethodModel extends SimpleMethod implements JavaMet
                     ") instead of obj.something will yield the desired value" };
         }
     }
-    
+
+    @Override
+    public String getMethodName() {
+        return getMember().getName();
+    }
+
+    @Override
+    public Class<?> getMethodDeclaringClass() {
+        return getMember().getDeclaringClass();
+    }
+
+    @Override
+    public String getOriginName() {
+        return getMethodDeclaringClass().getName() + "." + getMethodName();
+    }
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
index 061cf51..749ccd5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/TemplateLanguageUtils.java
@@ -743,34 +743,78 @@ public final class TemplateLanguageUtils {
     }
 
     /**
-     * Return the template language type name of callable class, as it should be shown in error messages.
+     * Return the template language type name of the given class, as it should be shown in error messages. This show
+     * less information that {@link #getTypeDescription(TemplateModel)}, but needs no instance, just a class.
+     *
+     * @param cl
+     *         The {@link TemplateModel} subclass; not {@code null}
+     */
+    public static String getTypeName(Class<? extends TemplateModel> cl) {
+        StringBuilder sb = new StringBuilder();
+        appendTemplateModelTypeName(sb, new HashSet<String>(4), cl);
+        return sb.toString();
+    }
+
+    /**
+     * Return the template language type name of the value as it should be shown in error messages, considering {@link
+     * TemplateCollectionModel} subinterfaces only.
      *
      * @param callable
      *         Can't be {@code null}.
      */
     public static String getCallableTypeName(TemplateCallableModel callable) {
-        _NullArgumentException.check("callable", callable);
-
         String result = "callable-of-unknown-kind";
 
         String d = null;
         if (callable instanceof TemplateDirectiveModel) {
-            d = _CoreAPI.isMacro(callable.getClass()) ? "macro" : "directive";
+            d = getDirectiveTypeName((TemplateDirectiveModel) callable);
             result = d;
         }
 
         if (callable instanceof TemplateFunctionModel) {
-            String f = callable instanceof JavaMethodModel ? "method" : "function";
+            String f = getFunctionTypeName((TemplateFunctionModel) callable);
             result = d == null ? f : d + "+" + f;
         }
 
         return result;
     }
 
+    public static String getCallableTypeName(TemplateCallableModel callable, boolean calledAsFunction) {
+        return calledAsFunction
+                ? getFunctionTypeName((TemplateFunctionModel) callable)
+                : !calledAsFunction ? getDirectiveTypeName(
+                        (TemplateDirectiveModel) callable)
+                        : getCallableTypeName(callable);
+    }
+
+    /**
+     * Return the template language type name of the value as it should be shown in error messages, considering
+     * the {@link TemplateFunctionModel} implementing part only.
+     *
+     * @param callable
+     *         Can't be {@code null}.
+     */
+    public static String getFunctionTypeName(TemplateFunctionModel callable) {
+        _NullArgumentException.check("callable", callable);
+        return callable instanceof JavaMethodModel ? "method" : "function";
+    }
+
+    /**
+     * Return the template language type name of the value as it should be shown in error messages, considering
+     * the {@link TemplateDirectiveModel} implementing part only.
+     *
+     * @param callable
+     *         Can't be {@code null}.
+     */
+    public static String getDirectiveTypeName(TemplateDirectiveModel callable) {
+        _NullArgumentException.check("callable", callable);
+        return _CoreAPI.isMacro(callable.getClass()) ? "macro" : "directive";
+    }
+
     /**
      * Returns the {@link TemplateModel} interface that is the most characteristic of the object, or {@code null}.
      */
-    private static Class getPrimaryTemplateModelInterface(TemplateModel tm) {
+    private static Class<? extends TemplateModel> getPrimaryTemplateModelInterface(TemplateModel tm) {
         if (tm instanceof BeanModel) {
             if (tm instanceof BeanAndStringModel) {
                 Object wrapped = ((BeanModel) tm).getWrappedObject();
@@ -785,8 +829,9 @@ public final class TemplateLanguageUtils {
         }
     }
 
-    private static void appendTemplateModelTypeName(StringBuilder sb, Set typeNamesAppended, Class cl) {
-        int initalLength = sb.length();
+    private static void appendTemplateModelTypeName(
+            StringBuilder sb, Set<String> typeNamesAppended, Class<? extends TemplateModel> cl) {
+        int initialLength = sb.length();
 
         if (TemplateNodeModelEx.class.isAssignableFrom(cl)) {
             appendTypeName(sb, typeNamesAppended, "extended node");
@@ -841,7 +886,7 @@ public final class TemplateLanguageUtils {
             appendTypeName(sb, typeNamesAppended, "markupOutput");
         }
 
-        if (sb.length() == initalLength) {
+        if (sb.length() == initialLength) {
             appendTypeName(sb, typeNamesAppended, "miscTemplateModel");
         }
     }
@@ -869,4 +914,5 @@ public final class TemplateLanguageUtils {
             typeNamesAppended.add(name);
         }
     }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
index 86b7d7e..057dd65 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
@@ -42,12 +42,13 @@ import org.apache.freemarker.core._TemplateModelException;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelWithOriginName;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.util._StringUtils;
 import org.apache.freemarker.servlet.jsp.SimpleTagDirectiveModel.TemplateExceptionWrapperJspException;
 
-class JspTagModelBase {
+abstract class JspTagModelBase implements TemplateModelWithOriginName {
     protected final String tagName;
     private final Class tagClass;
     private final Map propertySetters = new HashMap();
@@ -159,5 +160,10 @@ class JspTagModelBase {
                 || eClass == ClassCastException.class
                 || eClass == IndexOutOfBoundsException.class;
     }
-    
+
+    @Override
+    public String getOriginName() {
+        // TODO Can't we know the namespace URI from somewhere?
+        return "jspCustomTag:" + tagName;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/e941aedc/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TemplateDirectiveModelAndTemplateFunctionModel.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TemplateDirectiveModelAndTemplateFunctionModel.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TemplateDirectiveModelAndTemplateFunctionModel.java
index e4d74d1..bbb7606 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TemplateDirectiveModelAndTemplateFunctionModel.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TemplateDirectiveModelAndTemplateFunctionModel.java
@@ -29,6 +29,7 @@ import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelWithOriginName;
 
 /**
  * Used when a custom JSP tag and an EL function uses the same name in a tag library, to invoke a single FTL value from
@@ -36,42 +37,51 @@ import org.apache.freemarker.core.model.TemplateModel;
  * value.
  */
 class TemplateDirectiveModelAndTemplateFunctionModel
-        implements TemplateDirectiveModel, TemplateFunctionModel {
+        implements TemplateDirectiveModel, TemplateFunctionModel, TemplateModelWithOriginName {
 
-    private final TemplateDirectiveModel templateDirectiveModel;
-    private final TemplateFunctionModel templateFunctionModel;
+    private final TemplateDirectiveModel directive;
+    private final TemplateFunctionModel function;
 
     TemplateDirectiveModelAndTemplateFunctionModel( //
-            TemplateDirectiveModel templateDirectiveModel, TemplateFunctionModel templateMethodModelEx) {
-        this.templateDirectiveModel = templateDirectiveModel;
-        this.templateFunctionModel = templateMethodModelEx;
+            TemplateDirectiveModel directive, TemplateFunctionModel function) {
+        this.directive = directive;
+        this.function = function;
     }
 
     @Override
     public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
             throws TemplateException, IOException {
-        templateDirectiveModel.execute(args, callPlace, out, env);
+        directive.execute(args, callPlace, out, env);
     }
 
     @Override
     public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
-        return templateDirectiveModel.getDirectiveArgumentArrayLayout();
+        return directive.getDirectiveArgumentArrayLayout();
     }
 
     @Override
     public boolean isNestedContentSupported() {
-        return templateDirectiveModel.isNestedContentSupported();
+        return directive.isNestedContentSupported();
     }
 
     @Override
     public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
-        return templateFunctionModel.getFunctionArgumentArrayLayout();
+        return function.getFunctionArgumentArrayLayout();
     }
 
     @Override
     public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
             throws TemplateException {
-        return templateFunctionModel.execute(args, callPlace, env);
+        return function.execute(args, callPlace, env);
+    }
+
+    @Override
+    public String getOriginName() {
+        return (directive instanceof  TemplateModelWithOriginName
+                        ? ((TemplateModelWithOriginName) directive).getOriginName() : "unknownCustomJspTag")
+                + "+"
+                + (function instanceof  TemplateModelWithOriginName
+                        ? ((TemplateModelWithOriginName) function).getOriginName() : "unknownCustomJspFunction");
     }
 
 }


Mime
View raw message