Return-Path: X-Original-To: archive-asf-public-internal@cust-asf2.ponee.io Delivered-To: archive-asf-public-internal@cust-asf2.ponee.io Received: from cust-asf.ponee.io (cust-asf.ponee.io [163.172.22.183]) by cust-asf2.ponee.io (Postfix) with ESMTP id 271EC200CF8 for ; Wed, 16 Aug 2017 01:08:56 +0200 (CEST) Received: by cust-asf.ponee.io (Postfix) id 245FD1679D7; Tue, 15 Aug 2017 23:08:56 +0000 (UTC) Delivered-To: archive-asf-public@cust-asf.ponee.io Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by cust-asf.ponee.io (Postfix) with SMTP id 2012A1679D2 for ; Wed, 16 Aug 2017 01:08:53 +0200 (CEST) Received: (qmail 13642 invoked by uid 500); 15 Aug 2017 23:08:53 -0000 Mailing-List: contact notifications-help@freemarker.incubator.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@freemarker.incubator.apache.org Delivered-To: mailing list notifications@freemarker.incubator.apache.org Received: (qmail 13633 invoked by uid 99); 15 Aug 2017 23:08:53 -0000 Received: from pnap-us-west-generic-nat.apache.org (HELO spamd4-us-west.apache.org) (209.188.14.142) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 15 Aug 2017 23:08:53 +0000 Received: from localhost (localhost [127.0.0.1]) by spamd4-us-west.apache.org (ASF Mail Server at spamd4-us-west.apache.org) with ESMTP id 8EBC6C0042 for ; Tue, 15 Aug 2017 23:08:52 +0000 (UTC) X-Virus-Scanned: Debian amavisd-new at spamd4-us-west.apache.org X-Spam-Flag: NO X-Spam-Score: -4.222 X-Spam-Level: X-Spam-Status: No, score=-4.222 tagged_above=-999 required=6.31 tests=[KAM_ASCII_DIVIDERS=0.8, RCVD_IN_DNSWL_HI=-5, RCVD_IN_MSPIKE_H3=-0.01, RCVD_IN_MSPIKE_WL=-0.01, RP_MATCHES_RCVD=-0.001, SPF_PASS=-0.001] autolearn=disabled Received: from mx1-lw-eu.apache.org ([10.40.0.8]) by localhost (spamd4-us-west.apache.org [10.40.0.11]) (amavisd-new, port 10024) with ESMTP id zEIwbvfozFtw for ; Tue, 15 Aug 2017 23:08:43 +0000 (UTC) Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by mx1-lw-eu.apache.org (ASF Mail Server at mx1-lw-eu.apache.org) with SMTP id 381345FC7B for ; Tue, 15 Aug 2017 23:08:41 +0000 (UTC) Received: (qmail 13508 invoked by uid 99); 15 Aug 2017 23:08:40 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Tue, 15 Aug 2017 23:08:40 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 4C9AAF324D; Tue, 15 Aug 2017 23:08:40 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: ddekany@apache.org To: notifications@freemarker.incubator.apache.org Date: Tue, 15 Aug 2017 23:08:40 -0000 Message-Id: X-Mailer: ASF-Git Admin Mailer Subject: [1/2] incubator-freemarker git commit: FREEMARKER-65: Utilities for argument validation: work in progress... so far only some internal cleanup. archived-at: Tue, 15 Aug 2017 23:08:56 -0000 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[] 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 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 expectedType, + TemplateFunctionModel callable) { + return newArgumentValueTypeException( + argValue, argIdx, expectedType, + callable, true); + } + + public static TemplateException newArgumentValueTypeException( + TemplateModel argValue, int argIdx, Class 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[] 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 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 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 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 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 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 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 cl) { + StringBuilder sb = new StringBuilder(); + appendTemplateModelTypeName(sb, new HashSet(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 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 typeNamesAppended, Class 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"); } }