Repository: incubator-freemarker Updated Branches: refs/heads/3 e941aedc1 -> f231e64fe
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/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 0f11a18..d1019e1 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,487 +19,27 @@ package org.apache.freemarker.core; -import static org.apache.freemarker.core.util.TemplateLanguageUtils.*; +import static org.apache.freemarker.core.util.TemplateLanguageUtils.getCallableTypeName; -import java.io.IOException; -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.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.CallableUtils; import org.apache.freemarker.core.util.StringToIndexMap; -import org.apache.freemarker.core.util.TemplateLanguageUtils; import org.apache.freemarker.core.util._CollectionUtils; /** * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! + * The published part of this API is in {@link CallableUtils}. */ -// TODO [FM3] Most functionality here should be made public on some way. Also BuiltIn-s has some duplicates utiltity -// methods for this functionality (checking arguments). Need to clean this up. -public final class _CallableUtils { - - public static final TemplateModel[] EMPTY_TEMPLATE_MODEL_ARRAY = new TemplateModel[0]; - - private _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 { - directive.execute( - getArgumentArrayWithNoArguments(directive.getDirectiveArgumentArrayLayout()), callPlace, out, env); - } - - public static TemplateModel executeWith0Arguments( - TemplateFunctionModel function, CallPlace callPlace, Environment env) - throws TemplateException { - return function.execute( - getArgumentArrayWithNoArguments(function.getFunctionArgumentArrayLayout()), callPlace, env); - } - - private static TemplateModel[] getArgumentArrayWithNoArguments(ArgumentArrayLayout argsLayout) { - int totalLength = argsLayout != null ? argsLayout.getTotalLength() : 0; - if (totalLength == 0) { - return EMPTY_TEMPLATE_MODEL_ARRAY; - } else { - TemplateModel[] args = new TemplateModel[totalLength]; - - int positionalVarargsArgumentIndex = argsLayout.getPositionalVarargsArgumentIndex(); - if (positionalVarargsArgumentIndex != -1) { - args[positionalVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE; - } - - int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex(); - if (namedVarargsArgumentIndex != -1) { - args[namedVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE; - } - - return args; - } - } - - // String arg: - - /** - * 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 castArgumentValueToString(args[argIndex], argIndex, callable, true, false); - } - - /** - * 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 castArgumentValueToString(args[argIndex], argIndex, callable, false, false); - } - - /** - * 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 castArgumentValueToString(args[argIndex], argIndex, callable, true, true); - } - - /** - * 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 castArgumentValueToString(args[argIndex], argIndex, callable, false, true); - } - - /** - * 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 TemplateScalarModel) { - return _EvalUtils.modelToString((TemplateScalarModel) argValue, null); - } - if (argValue == null) { - if (optional) { - return null; - } - throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction); - } - 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 Number getNumberArgument( - TemplateModel[] args, int argIndex, TemplateDirectiveModel callable) - throws TemplateException { - return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, false); - } - - 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 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); - } - - // 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); - } - - /** 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); - } - - /** 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); - } - - /** 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); - } - - /** 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)); - } - } - - 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)"); - } - } - 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(); - } - - // - +public class _CallableUtils { static TemplateModel[] getExecuteArgs( ASTExpression[] positionalArgs, NamedArgument[] namedArgs, ArgumentArrayLayout argsLayout, TemplateCallableModel callable, boolean calledAsFunction, @@ -528,7 +68,7 @@ public final class _CallableUtils { execArgs[i] = positionalArg.eval(env); } } else { - execArgs = EMPTY_TEMPLATE_MODEL_ARRAY; + execArgs = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; } return execArgs; } @@ -638,7 +178,7 @@ public final class _CallableUtils { } private static Object[] getNamedArgumentsNotSupportedMessage(TemplateCallableModel callable, - NamedArgument namedArg, boolean calledAsFunction) { + _CallableUtils.NamedArgument namedArg, boolean calledAsFunction) { return new Object[] { getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), "This ", getCallableTypeName(callable, calledAsFunction), @@ -663,6 +203,24 @@ public final class _CallableUtils { } } + /** + * Something like {@code "When calling function \"lib.ftl:foo\": " or "When calling ?leftPad: "} + */ + public 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()), + ": " + }; + } + static final class NamedArgument { private final String name; private final ASTExpression value; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java index c904afa..df7042b 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_CoreAPI.java @@ -69,6 +69,10 @@ public final class _CoreAPI { return Environment.TemplateLanguageFunction.class.isAssignableFrom(cl); } + public static boolean isTemplateLanguageCallable(Class<? extends TemplateModel> cl) { + return Environment.TemplateLanguageCallable.class.isAssignableFrom(cl); + } + public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) { _NullArgumentException.check("incompatibleImprovements", incompatibleImprovements); int iciV = incompatibleImprovements.intValue(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/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 e9a7967..17f6459 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 @@ -19,6 +19,8 @@ package org.apache.freemarker.core; +import static org.apache.freemarker.core.MessageUtils.*; + import java.util.Date; import org.apache.freemarker.core.arithmetic.ArithmeticEngine; @@ -57,7 +59,7 @@ public class _EvalUtils { /** * @param expr {@code null} is allowed, but may results in less helpful error messages */ - static String modelToString(TemplateScalarModel model, ASTExpression expr) + public static String modelToString(TemplateScalarModel model, ASTExpression expr) throws TemplateModelException { String value = model.getAsString(); if (value == null) { @@ -69,7 +71,7 @@ public class _EvalUtils { /** * @param expr {@code null} is allowed, but may results in less helpful error messages */ - static Number modelToNumber(TemplateNumberModel model, ASTExpression expr) + public static Number modelToNumber(TemplateNumberModel model, ASTExpression expr) throws TemplateModelException { Number value = model.getAsNumber(); if (value == null) throw newModelHasStoredNullException(Number.class, model, expr); @@ -79,7 +81,7 @@ public class _EvalUtils { /** * @param expr {@code null} is allowed, but may results in less helpful error messages */ - static Date modelToDate(TemplateDateModel model, ASTExpression expr) + public static Date modelToDate(TemplateDateModel model, ASTExpression expr) throws TemplateModelException { Date value = model.getAsDate(); if (value == null) throw newModelHasStoredNullException(Date.class, model, expr); @@ -447,7 +449,7 @@ public class _EvalUtils { throw InvalidReferenceException.getInstance(exp, env); } else { throw new InvalidReferenceException( - "Null/missing value (no more informatoin avilable)", + "Null/missing value (no more information available)", env); } } else if (tm instanceof TemplateBooleanModel) { @@ -459,37 +461,31 @@ public class _EvalUtils { if (returnNullOnNonCoercableType) { return null; } - if (seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)) { - if (supportsTOM) { - throw new NonStringOrTemplateOutputException(exp, tm, seqHint, env); - } else { - throw new NonStringException(exp, tm, seqHint, env); - } - } else { - if (supportsTOM) { - throw new NonStringOrTemplateOutputException(exp, tm, env); - } else { - throw new NonStringException(exp, tm, env); - } - } + + throw newUnexpectedOperandTypeException( + exp, tm, + supportsTOM ? STRING_COERCABLE_TYPES_OR_TOM_DESC : STRING_COERCABLE_TYPES_DESC, + supportsTOM ? EXPECTED_TYPES_STRING_COERCABLE_TYPES_AND_TOM : EXPECTED_TYPES_STRING_COERCABLE, + seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel) + ? new Object[] { seqHint } + : null, + env); } } private static String ensureFormatResultString(Object formatResult, ASTExpression exp, Environment env) - throws NonStringException { + throws TemplateException { if (formatResult instanceof String) { return (String) formatResult; } assertFormatResultNotNull(formatResult); - TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) formatResult; - _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder( + throw new TemplateException(env, new _ErrorDescriptionBuilder( "Value was formatted to convert it to string, but the result was markup of ouput format ", - new _DelayedJQuote(mo.getOutputFormat()), ".") + new _DelayedJQuote(((TemplateMarkupOutputModel) formatResult).getOutputFormat()), ".") .tip("Use value?string to force formatting to plain text.") - .blame(exp); - throw new NonStringException(null, desc); + .blame(exp)); } static String assertFormatResultNotNull(String r) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java index 285021c..ecd4ebb 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java @@ -34,7 +34,11 @@ implements TemplateBooleanModel, TemplateScalarModel, TemplateSequenceModel, Tem TemplateFunctionModel { public static final TemplateModel INSTANCE = new GeneralPurposeNothing(); - + + private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create( + 0, true, + null, true); + private GeneralPurposeNothing() { } @@ -75,9 +79,7 @@ implements TemplateBooleanModel, TemplateScalarModel, TemplateSequenceModel, Tem @Override public ArgumentArrayLayout getFunctionArgumentArrayLayout() { - return ArgumentArrayLayout.create( - 0, true, - null, true); + return ARGS_LAYOUT; } @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java index 2f1a506..7f57c68 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedFixArgsMethods.java @@ -18,7 +18,7 @@ */ package org.apache.freemarker.core.model.impl; -import org.apache.freemarker.core._CallableUtils; +import org.apache.freemarker.core.util.CallableUtils; import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelException; @@ -47,7 +47,7 @@ class OverloadedFixArgsMethods extends OverloadedMethodsSubset { throws TemplateModelException { if (tmArgs == null) { // null is treated as empty args - tmArgs = _CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; + tmArgs = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; } final int argCount = tmArgs.length; final Class<?>[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java index 8b325ed..fd5c5ee 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/OverloadedVarArgsMethods.java @@ -20,7 +20,7 @@ package org.apache.freemarker.core.model.impl; import java.lang.reflect.Array; -import org.apache.freemarker.core._CallableUtils; +import org.apache.freemarker.core.util.CallableUtils; import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelException; @@ -139,7 +139,7 @@ class OverloadedVarArgsMethods extends OverloadedMethodsSubset { throws TemplateModelException { if (tmArgs == null) { // null is treated as empty args - tmArgs = _CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; + tmArgs = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; } final int argsLen = tmArgs.length; final Class<?>[][] unwrappingHintsByParamCount = getUnwrappingHintsByParamCount(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/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 f794083..b5fb41c 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 @@ -30,7 +30,7 @@ import java.util.Set; import org.apache.freemarker.core.CallPlace; import org.apache.freemarker.core.Environment; import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core._CallableUtils; +import org.apache.freemarker.core.util.CallableUtils; import org.apache.freemarker.core._DelayedJQuote; import org.apache.freemarker.core._TemplateModelException; import org.apache.freemarker.core.model.ArgumentArrayLayout; @@ -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.getStringArgument(args, 0, this); + 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/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java index c1786af..78c624c 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleMethod.java @@ -21,7 +21,7 @@ package org.apache.freemarker.core.model.impl; import java.lang.reflect.Array; import java.lang.reflect.Member; -import org.apache.freemarker.core._CallableUtils; +import org.apache.freemarker.core.util.CallableUtils; import org.apache.freemarker.core._DelayedTemplateLanguageTypeDescription; import org.apache.freemarker.core._DelayedOrdinal; import org.apache.freemarker.core._ErrorDescriptionBuilder; @@ -52,7 +52,7 @@ class SimpleMethod { Object[] unwrapArguments(TemplateModel[] args, DefaultObjectWrapper wrapper) throws TemplateModelException { if (args == null) { - args = _CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; + args = CallableUtils.EMPTY_TEMPLATE_MODEL_ARRAY; } boolean isVarArg = _MethodUtils.isVarargs(member); int typesLen = argTypes.length; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java new file mode 100644 index 0000000..a208612 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java @@ -0,0 +1,496 @@ +/* + * 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.util; + +import java.io.IOException; +import java.io.Writer; +import java.util.ArrayList; + +import org.apache.freemarker.core.CallPlace; +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core._CallableUtils; +import org.apache.freemarker.core._DelayedAOrAn; +import org.apache.freemarker.core._DelayedJQuote; +import org.apache.freemarker.core._DelayedOrdinal; +import org.apache.freemarker.core._DelayedTemplateLanguageTypeDescription; +import org.apache.freemarker.core._ErrorDescriptionBuilder; +import org.apache.freemarker.core._EvalUtils; +import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel; +import org.apache.freemarker.core.model.ArgumentArrayLayout; +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.TemplateModel; +import org.apache.freemarker.core.model.TemplateNumberModel; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; + +/** + * For internal use only; don't depend on this, there's no backward compatibility guarantee at all! + */ +public final class CallableUtils { + + public static final TemplateModel[] EMPTY_TEMPLATE_MODEL_ARRAY = new TemplateModel[0]; + + private 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( + _CallableUtils.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 _newArgumentValueException(argIdx, problemDescription, callable, calledAsFunction, null); + } + + // TODO [FM3] How to expose tips API? + public static TemplateException _newArgumentValueException( + int argIdx, String problemDescription, + TemplateCallableModel callable, boolean calledAsFunction, + Object[] tips) { + return new TemplateException( + new _ErrorDescriptionBuilder( + getMessageArgumentProblem(callable, argIdx, problemDescription, calledAsFunction) + ).tips(tips)); + } + + /** + * 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 _newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction, null); + } + + // TODO [FM3] How to expose tips API? + public static TemplateException _newNullOrOmittedArgumentException(int argIdx, TemplateCallableModel callable, + boolean calledAsFunction, Object[] tips) { + return _newArgumentValueException(argIdx, "can't be omitted or null.", callable, calledAsFunction, tips); + } + + 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[] { + _CallableUtils.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 { + directive.execute( + getArgumentArrayWithNoArguments(directive.getDirectiveArgumentArrayLayout()), callPlace, out, env); + } + + public static TemplateModel executeWith0Arguments( + TemplateFunctionModel function, CallPlace callPlace, Environment env) + throws TemplateException { + return function.execute( + getArgumentArrayWithNoArguments(function.getFunctionArgumentArrayLayout()), callPlace, env); + } + + private static TemplateModel[] getArgumentArrayWithNoArguments(ArgumentArrayLayout argsLayout) { + int totalLength = argsLayout != null ? argsLayout.getTotalLength() : 0; + if (totalLength == 0) { + return EMPTY_TEMPLATE_MODEL_ARRAY; + } else { + TemplateModel[] args = new TemplateModel[totalLength]; + + int positionalVarargsArgumentIndex = argsLayout.getPositionalVarargsArgumentIndex(); + if (positionalVarargsArgumentIndex != -1) { + args[positionalVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE; + } + + int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex(); + if (namedVarargsArgumentIndex != -1) { + args[namedVarargsArgumentIndex] = TemplateSequenceModel.EMPTY_SEQUENCE; + } + + return args; + } + } + + // String arg: + + /** + * 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 castArgumentValueToString(args[argIndex], argIndex, callable, true, false); + } + + /** + * 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 castArgumentValueToString(args[argIndex], argIndex, callable, false, false); + } + + /** + * 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 castArgumentValueToString(args[argIndex], argIndex, callable, true, true); + } + + /** + * 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 castArgumentValueToString(args[argIndex], argIndex, callable, false, true); + } + + /** + * 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 TemplateScalarModel) { + return _EvalUtils.modelToString((TemplateScalarModel) argValue, null); + } + if (argValue == null) { + if (optional) { + return null; + } + throw newNullOrOmittedArgumentException(argIdx, callable, calledAsFunction); + } + 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 Number getNumberArgument( + TemplateModel[] args, int argIndex, TemplateDirectiveModel callable) + throws TemplateException { + return castArgumentValueToNumber(args[argIndex], argIndex, callable, false, false); + } + + 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 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); + } + + // 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); + } + + /** 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); + } + + /** 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); + } + + /** 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); + } + + /** 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( + _CallableUtils.getMessagePartWhenCallingSomethingColon(callable, calledAsFunction), + getMessagePartExpectedNArgumentButHadM(argCnt, minCnt, maxCnt)); + } + } + + 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)"); + } + } + 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(); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/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 749ccd5..9b980d9 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 @@ -840,12 +840,18 @@ public final class TemplateLanguageUtils { } if (TemplateCallableModel.class.isAssignableFrom(cl)) { + boolean recognized = false; if (TemplateDirectiveModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, _CoreAPI.isMacro(cl) ? "macro" : "directive"); + recognized = true; } if (TemplateFunctionModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, JavaMethodModel.class.isAssignableFrom(cl) ? "method" : "function"); + recognized = true; + } + if (!recognized && _CoreAPI.isTemplateLanguageCallable(cl)) { + appendTypeName(sb, typeNamesAppended, "macro or function defined with template language"); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f231e64f/freemarker-core/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj index 613b252..4a3295f 100644 --- a/freemarker-core/src/main/javacc/FTL.jj +++ b/freemarker-core/src/main/javacc/FTL.jj @@ -2248,8 +2248,8 @@ ASTDollarInterpolation ASTDollarInterpolation() : begin = <DOLLAR_INTERPOLATION_OPENING> exp = ASTExpression() { - notHashLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); - notListLiteral(exp, NonStringException.STRING_COERCABLE_TYPES_DESC); + notHashLiteral(exp, MessageUtils.STRING_COERCABLE_TYPES_DESC); + notListLiteral(exp, MessageUtils.STRING_COERCABLE_TYPES_DESC); } end = <CLOSING_CURLY_BRACKET> {
