You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/08/07 22:32:20 UTC
[18/21] incubator-freemarker git commit: FREEMARKER-63: Lot of
refinement in the API-s and implementation. #macro now creates a
`TemplateDirectiveModel`,
and #function now creates `TemplateFunctionModel` (though the function/method
call syntax doesn't ye
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
index e9b2c4e..b67d54d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTElement.java
@@ -389,7 +389,7 @@ abstract class ASTElement extends ASTNode {
private ASTElement getFirstLeaf() {
ASTElement te = this;
- while (!te.isLeaf() && !(te instanceof ASTDirMacro) && !(te instanceof ASTDirCapturingAssignment)) {
+ while (!te.isLeaf() && !(te instanceof ASTDirMacroOrFunction) && !(te instanceof ASTDirCapturingAssignment)) {
// A macro or macro invocation is treated as a leaf here for special reasons
te = te.getFirstChild();
}
@@ -398,7 +398,7 @@ abstract class ASTElement extends ASTNode {
private ASTElement getLastLeaf() {
ASTElement te = this;
- while (!te.isLeaf() && !(te instanceof ASTDirMacro) && !(te instanceof ASTDirCapturingAssignment)) {
+ while (!te.isLeaf() && !(te instanceof ASTDirMacroOrFunction) && !(te instanceof ASTDirCapturingAssignment)) {
// A macro or macro invocation is treated as a leaf here for special reasons
te = te.getLastChild();
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
index 74ae72f..ab50470 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
@@ -145,7 +145,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
return env.getGlobalVariables();
}
if (name == LOCALS) {
- ASTDirMacro.Context ctx = env.getCurrentMacroContext();
+ ASTDirMacroOrFunction.Context ctx = env.getCurrentMacroContext();
return ctx == null ? null : ctx.getLocals();
}
if (name == DATA_MODEL) {
@@ -173,7 +173,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
return SimpleScalar.newInstanceOrNull(env.getCurrentTemplate().getLookupName());
}
if (name == PASS) {
- return ASTDirMacro.DO_NOTHING_MACRO;
+ return ASTDirMacroOrFunction.PASS_MACRO;
}
if (name == OUTPUT_ENCODING) {
Charset encoding = env.getOutputEncoding();
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
index 1612a79..e5dc679 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
@@ -81,11 +81,11 @@ final class ASTExpListLiteral extends ASTExpression {
/**
* For {@link TemplateMethodModelEx} calls, returns the list of arguments as {@link TemplateModel}-s.
*/
- List/*<TemplateModel>*/ getModelList(Environment env) throws TemplateException {
+ List<TemplateModel> getModelList(Environment env) throws TemplateException {
int size = items.size();
switch(size) {
case 0: {
- return Collections.EMPTY_LIST;
+ return Collections.emptyList();
}
case 1: {
return Collections.singletonList(((ASTExpression) items.get(0)).eval(env));
@@ -101,6 +101,10 @@ final class ASTExpListLiteral extends ASTExpression {
}
}
+ public int size() {
+ return items.size();
+ }
+
@Override
public String getCanonicalForm() {
StringBuilder buf = new StringBuilder("[");
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
index 4f51904..dc8f3ff 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpMethodCall.java
@@ -28,16 +28,21 @@ import java.io.Writer;
import java.util.ArrayList;
import java.util.List;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
import org.apache.freemarker.core.model.TemplateMethodModel;
import org.apache.freemarker.core.model.TemplateMethodModelEx;
import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.util._NullWriter;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.CommonSupplier;
+import org.apache.freemarker.core.util.FTLUtil;
/**
* AST expression node: {@code exp(args)}.
*/
-final class ASTExpMethodCall extends ASTExpression {
+final class ASTExpMethodCall extends ASTExpression implements CallPlace {
private final ASTExpression target;
private final ASTExpListLiteral arguments;
@@ -62,23 +67,55 @@ final class ASTExpMethodCall extends ASTExpression {
: arguments.getValueList(env);
Object result = targetMethod.exec(argumentStrings);
return env.getObjectWrapper().wrap(result);
- } else if (targetModel instanceof ASTDirMacro) {
- ASTDirMacro func = (ASTDirMacro) targetModel;
- env.setLastReturnValue(null);
- if (!func.isFunction()) {
- throw new _MiscTemplateException(env, "A macro cannot be called in an expression. (Functions can be.)");
+ } else if (targetModel instanceof TemplateFunctionModel) {
+ TemplateFunctionModel func = (TemplateFunctionModel) targetModel;
+
+ ArgumentArrayLayout arrayLayout = func.getArgumentArrayLayout();
+
+ // TODO [FM3] This is just temporary, until we support named args. Then the logic in ASTDynamicTopLevelCall
+ // should be reused.
+
+ int posVarargsLength;
+ int callArgCnt = arguments.size();
+ int predefPosArgCnt = arrayLayout.getPredefinedPositionalArgumentCount();
+ int posVarargsIdx = arrayLayout.getPositionalVarargsArgumentIndex();
+ if (callArgCnt > predefPosArgCnt) {
+ if (posVarargsIdx == -1) {
+ throw new _MiscTemplateException(env,
+ "Too many arguments; the target ", FTLUtil.getCallableTypeName(func),
+ " has ", predefPosArgCnt, " arguments.");
+ }
}
- Writer prevOut = env.getOut();
- try {
- env.setOut(_NullWriter.INSTANCE);
- env.invoke(func, null, arguments.items, null, null);
- } catch (IOException e) {
- // Should not occur
- throw new TemplateException("Unexpected exception during function execution", e, env);
- } finally {
- env.setOut(prevOut);
+
+ List<TemplateModel> callArgList = arguments.getModelList(env);
+
+ TemplateModel[] args = new TemplateModel[arrayLayout.getTotalLength()];
+ int callPredefArgCnt = Math.min(callArgCnt, predefPosArgCnt);
+ for (int argIdx = 0; argIdx < callPredefArgCnt; argIdx++) {
+ args[argIdx] = callArgList.get(argIdx);
}
- return env.getLastReturnValue();
+
+ if (posVarargsIdx != -1) {
+ TemplateSequenceModel varargsSeq;
+ posVarargsLength = callArgCnt - predefPosArgCnt;
+ if (posVarargsLength <= 0) {
+ varargsSeq = Constants.EMPTY_SEQUENCE;
+ } else {
+ NativeSequence nativeSeq = new NativeSequence(posVarargsLength);
+ varargsSeq = nativeSeq;
+ for (int posVarargIdx = 0; posVarargIdx < posVarargsLength; posVarargIdx++) {
+ nativeSeq.add(callArgList.get(predefPosArgCnt + posVarargIdx));
+ }
+ }
+ args[posVarargsIdx] = varargsSeq;
+ }
+
+ int namedVarargsArgIdx = arrayLayout.getNamedVarargsArgumentIndex();
+ if (namedVarargsArgIdx != -1) {
+ args[namedVarargsArgIdx] = Constants.EMPTY_HASH;
+ }
+
+ return func.execute(args, this, env);
} else {
throw new NonMethodException(target, targetModel, env);
}
@@ -144,4 +181,50 @@ final class ASTExpMethodCall extends ASTExpression {
}
}
+ // -----------------------------------------------------------------------------------------------------------------
+ // CallPlace API
+
+ @Override
+ public boolean hasNestedContent() {
+ return false;
+ }
+
+ @Override
+ public int getNestedContentParameterCount() {
+ return 0;
+ }
+
+ @Override
+ public void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env)
+ throws TemplateException, IOException {
+ // Do nothing
+ }
+
+ @Override
+ public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
+ throws CallPlaceCustomDataInitializationException {
+ throw new UnsupportedOperationException("Expression call places don't store custom data");
+ }
+
+ @Override
+ public boolean isCustomDataSupported() {
+ return false;
+ }
+
+ @Override
+ public boolean isNestedOutputCacheable() {
+ return false;
+ }
+
+ @Override
+ public int getFirstTargetJavaParameterTypeIndex() {
+ // TODO [FM3]
+ return -1;
+ }
+
+ @Override
+ public Class<?> getTargetJavaParameterType(int argIndex) {
+ // TODO [FM3]
+ return null;
+ }
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
index 1ed4d2d..eceb412 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTStaticText.java
@@ -370,7 +370,7 @@ final class ASTStaticText extends ASTElement {
private boolean nonOutputtingType(ASTElement element) {
- return (element instanceof ASTDirMacro ||
+ return (element instanceof ASTDirMacroOrFunction ||
element instanceof ASTDirAssignment ||
element instanceof ASTDirAssignmentsContainer ||
element instanceof ASTDirSetting ||
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
index 467a2f4..74f7c15 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -312,8 +312,8 @@ class BuiltInsForMultipleTypes {
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
- // WRONG: it also had to check ASTDirMacro.isFunction()
- return (tm instanceof ASTDirMacro || tm instanceof TemplateDirectiveModel) ?
+ // WRONG: it also had to check ASTDirMacroOrFunction.isFunction()
+ return (tm instanceof ASTDirMacroOrFunction || tm instanceof TemplateDirectiveModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
@@ -360,8 +360,7 @@ class BuiltInsForMultipleTypes {
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
- // WRONG: it also had to check ASTDirMacro.isFunction()
- return (tm instanceof ASTDirMacro) ?
+ return (tm instanceof Environment.TemplateLanguageDirective) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
@@ -430,14 +429,13 @@ class BuiltInsForMultipleTypes {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
- if (!(tm instanceof ASTDirMacro)) {
+ if (!(tm instanceof Environment.TemplateLanguageCallable)) {
throw new UnexpectedTypeException(
target, tm,
- "macro or function", new Class[] { ASTDirMacro.class },
+ "macro or function", new Class[] { Environment.TemplateLanguageCallable.class },
env);
- } else {
- return env.getMacroNamespace((ASTDirMacro) tm);
}
+ return ((Environment.TemplateLanguageCallable) tm).getNamespace();
}
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index d9ad1bf..2d50556 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -25,7 +25,6 @@ import java.io.Writer;
import java.util.List;
import org.apache.freemarker.core.model.ArgumentArrayLayout;
-import org.apache.freemarker.core.model.CallPlace;
import org.apache.freemarker.core.model.ObjectWrapper;
import org.apache.freemarker.core.model.TemplateBooleanModel;
import org.apache.freemarker.core.model.TemplateDirectiveModel;
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
new file mode 100644
index 0000000..628c9ec
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlace.java
@@ -0,0 +1,195 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.IdentityHashMap;
+
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.util.CommonSupplier;
+
+/**
+ * The place (in a template, usually) from where a directive (like a macro) or function was called;
+ * <b>Do not implement this interface yourself</b>, as new methods may be added any time! Only FreeMarker itself
+ * should provide implementations. In case you have to call something from outside a template, use
+ * {@link NonTemplateCallPlace#INSTANCE}.
+ */
+public interface CallPlace {
+
+ // -------------------------------------------------------------------------------------------------------------
+ // Nested content:
+
+ /**
+ * Tells if there's a non-zero-length nested content. This is {@code false} for {@code <@foo />} or
+ * {@code <@f...@foo>} or for calls inside expressions (i.e., for function calls).
+ */
+ boolean hasNestedContent();
+
+ /**
+ * The number of nested content parameters in this call (like 2 in {@code <@foo xs; k, v>...</@>}). If you want the
+ * caller to specify a fixed number of nested content parameters, then this is not interesting for you, and just
+ * pass an array of that length to {@link #executeNestedContent(TemplateModel[], Writer, Environment)}. If, however,
+ * you want to allow the caller to declare less parameters, then this is how you know how much parameters you should
+ * calculate and pass to {@link #executeNestedContent(TemplateModel[], Writer, Environment)}.
+ */
+ int getNestedContentParameterCount();
+
+ /**
+ * Executes the nested content; it there's none, it just does nothing.
+ *
+ * @param nestedContentArgs
+ * The nested content parameter values to pass to the nested content (as in {@code <@foo bar; i, j>${i},
+ * ${j}</...@foo>} there are 2 such parameters, whose value you set here), or {@code null} if there's none.
+ * This array must be {@link #getNestedContentParameterCount()} long, or else FreeMarker will throw an
+ * {@link TemplateException} with a descriptive error message that tells to user that they need to declare
+ * that many nested content parameters as the length of this array. If you want to allow the caller to not
+ * declare some of the nested content parameters, then you have to make this array shorter according to
+ * {@link #getNestedContentParameterCount()}.
+ */
+ void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env)
+ throws TemplateException, IOException;
+
+ // -------------------------------------------------------------------------------------------------------------
+ // Source code info:
+
+ /**
+ * The template that contains this call; {@code null} if the call is not from a template (but directly from
+ * user Java code, for example).
+ */
+ Template getTemplate();
+
+ /**
+ * The 1-based column number of the first character of the directive call in the template source code, or -1 if it's
+ * not known.
+ */
+ int getBeginColumn();
+
+ /**
+ * The 1-based line number of the first character of the directive call in the template source code, or -1 if it's
+ * not known.
+ */
+ int getBeginLine();
+
+ /**
+ * The 1-based column number of the last character of the directive call in the template source code, or -1 if it's
+ * not known. If the directive has an end-tag ({@code </...@...>}), then it points to the last character of that.
+ */
+ int getEndColumn();
+
+ /**
+ * The 1-based line number of the last character of the directive call in the template source code, or -1 if it's
+ * not known. If the directive has an end-tag ({@code </...@...>}), then it points to the last character of that.
+ */
+ int getEndLine();
+
+ // -------------------------------------------------------------------------------------------------------------
+ // Caching:
+
+ /**
+ * Returns the custom data, or if that's {@code null}, then it creates and stores it in an atomic operation then
+ * returns it. This method is thread-safe, however, it doesn't ensure thread safe (like synchronized) access to the
+ * custom data itself. Be sure that the custom data only depends on things that get their final value during
+ * template parsing, not on runtime settings.
+ * <p>
+ * This method will block other calls while the {@code supplier} is executing, thus, the object will be
+ * <em>usually</em> created only once, even if multiple threads request the value when it's still {@code null}. It
+ * doesn't stand though when {@code providerIdentity} mismatches occur (see later). Furthermore, then it's also
+ * possible that multiple objects created by the same {@link CommonSupplier} will be in use on the same time,
+ * because of directive executions already running in parallel, and because of memory synchronization delays
+ * (hardware dependent) between the threads.
+ *
+ * @param providerIdentity
+ * This is usually the class of the {@link TemplateDirectiveModel} that creates (and uses) the custom data,
+ * or if you are using your own class for the custom data object (as opposed to a class from some more
+ * generic API), then that class. This is needed as the same call place might calls different directives
+ * depending on runtime conditions, and so it must be ensured that these directives won't accidentally read
+ * each other's custom data, ending up with class cast exceptions or worse. In the current implementation,
+ * if there's a {@code providerIdentity} mismatch (means, the {@code providerIdentity} object used when the
+ * custom data was last set isn't the exactly same object as the one provided with the parameter now), the
+ * previous custom data will be just ignored as if it was {@code null}. So if multiple directives that use
+ * the custom data feature use the same call place, the caching of the custom data can be inefficient, as
+ * they will keep overwriting each other's custom data. (In a more generic implementation the {@code
+ * providerIdentity} would be a key in a {@link IdentityHashMap}, but then this feature would be slower,
+ * while {@code providerIdentity} mismatches aren't occurring in most applications.)
+ * @param supplier
+ * Called when the custom data wasn't yet set, to invoke its initial value. If this parameter is {@code
+ * null} and the custom data wasn't set yet, then {@code null} will be returned. The returned value of
+ * {@link CommonSupplier#get()} can be any kind of object, but can't be {@code null}.
+ *
+ * @return The current custom data object, or possibly {@code null} if there was no {@link CommonSupplier} provided.
+ *
+ * @throws CallPlaceCustomDataInitializationException
+ * If the {@link CommonSupplier} had to be invoked but failed.
+ * @throws UnsupportedOperationException
+ * If this call place doesn't support storing custom date; see {@link #isCustomDataSupported()}.
+ */
+ Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
+ throws CallPlaceCustomDataInitializationException;
+
+ /**
+ * Tells if this call place supports storing custom data. As of this writing, only top-level (i.e., outside
+ * expression) directive calls do.
+ */
+ boolean isCustomDataSupported();
+
+ /**
+ * Tells if the output of the nested content can be safely cached, as it only depends on the template content (not
+ * on variable values and such) and has no side-effects (other than writing to the output). Examples of cases that
+ * give {@code false}: {@code <@foo>Name: } <tt...@foo>}, {@code <@foo>Name: <#if
+ * condition>bar</#...@foo>}. Examples of cases that give {@code true}: {@code <@foo>Name: Joe</...@foo>}, {@code
+ * <@foo />}. Note that we get {@code true} for no nested content, because that's equivalent to 0-length nested
+ * content.
+ * <p>
+ * This method returns a pessimistic result. For example, if it sees a custom directive call, it can't know what it
+ * does, so it will assume that it's not cacheable.
+ */
+ boolean isNestedOutputCacheable();
+
+ // -------------------------------------------------------------------------------------------------------------
+ // Overloaded method selection:
+
+ /**
+ * The index of the first item in the argument array passed to {@code execute} that has this information.
+ * Used solely for speed optimization (to minimize the number of
+ * {@link #getTargetJavaParameterType(int)} calls).
+ *
+ * @return -1 if no parameter has type hint
+ */
+ int getFirstTargetJavaParameterTypeIndex();
+
+ /**
+ * The type of the parameter in the target Java method; used for overloaded Java method selection. This optional
+ * information is specified by the template author in the source code (the syntax is not yet decided when I write
+ * this).
+ *
+ * @param argIndex
+ * The index of the argument in the argument array
+ *
+ * @return The desired Java type or {@code null} if this information wasn't specified in the template.
+ *
+ * @throws IndexOutOfBoundsException
+ * Might be thrown if {@code argIndex} is an invalid index according the number of arguments on the call
+ * site. Some implementations may just return {@code null} in that case though.
+ */
+ Class<?> getTargetJavaParameterType(int argIndex);
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
index 0033afd..9c4d22e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
@@ -19,13 +19,12 @@
package org.apache.freemarker.core;
-import org.apache.freemarker.core.model.CallPlace;
import org.apache.freemarker.core.util.CommonSupplier;
/**
* Thrown by {@link CallPlace#getOrCreateCustomData(Object, CommonSupplier)}
*/
-public class CallPlaceCustomDataInitializationException extends Exception {
+public class CallPlaceCustomDataInitializationException extends RuntimeException {
public CallPlaceCustomDataInitializationException(String message, Throwable cause) {
super(message, cause);
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
index af798b4..25ea39d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
@@ -43,10 +43,13 @@ import java.util.Set;
import java.util.TimeZone;
import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCallableModel;
import org.apache.freemarker.core.model.TemplateCollectionModel;
import org.apache.freemarker.core.model.TemplateDateModel;
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.TemplateHashModelEx;
import org.apache.freemarker.core.model.TemplateModel;
@@ -164,7 +167,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
private Collator cachedCollator;
private Writer out;
- private ASTDirMacro.Context currentMacroContext;
+ private ASTDirMacroOrFunction.Context currentMacroContext;
private LocalContextStack localContextStack;
private final Template mainTemplate;
private final Namespace mainNamespace;
@@ -175,7 +178,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
private Throwable lastThrowable;
private TemplateModel lastReturnValue;
- private HashMap macroToNamespaceLookup = new HashMap();
private TemplateNodeModel currentVisitorNode;
private TemplateSequenceModel nodeNamespaces;
@@ -517,28 +519,25 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
}
/**
- * Used for {@code #nested}.
+ * Used to execute the nested content of a macro call during a macro execution. It's not enough to simply call
+ * {@link CallPlace#executeNestedContent(TemplateModel[], Writer, Environment)} in such case, because an executing
+ * macro modifies the template language environment for its purposes, and the nested content expects that to be
+ * like before the macro invocation. So things has to be temporarily restored.
*/
- void invokeNestedContent(ASTDirNested.Context bodyCtx) throws TemplateException, IOException {
- ASTDirMacro.Context invokingMacroContext = getCurrentMacroContext();
- LocalContextStack prevLocalContextStack = localContextStack;
- ASTElement[] nestedContentBuffer = invokingMacroContext.nestedContentBuffer;
- if (nestedContentBuffer != null) {
+ void executeNestedContentOfMacro(TemplateModel[] nestedContentParamValues)
+ throws TemplateException, IOException {
+ ASTDirMacroOrFunction.Context invokingMacroContext = getCurrentMacroContext();
+ CallPlace callPlace = invokingMacroContext.callPlace;
+ if (callPlace.hasNestedContent()) {
currentMacroContext = invokingMacroContext.prevMacroContext;
currentNamespace = invokingMacroContext.nestedContentNamespace;
-
+ LocalContextStack prevLocalContextStack = localContextStack;
localContextStack = invokingMacroContext.prevLocalContextStack;
- if (invokingMacroContext.nestedContentParameterNames != null) {
- pushLocalContext(bodyCtx);
- }
try {
- visit(nestedContentBuffer);
+ callPlace.executeNestedContent(nestedContentParamValues, out, this);
} finally {
- if (invokingMacroContext.nestedContentParameterNames != null) {
- popLocalContext();
- }
currentMacroContext = invokingMacroContext;
- currentNamespace = getMacroNamespace(invokingMacroContext.getMacro());
+ currentNamespace = invokingMacroContext.callable.namespace;
localContextStack = prevLocalContextStack;
}
}
@@ -580,18 +579,20 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
nodeNamespaces = namespaces;
}
try {
- TemplateModel macroOrDirective = getNodeProcessor(node);
- if (macroOrDirective instanceof ASTDirMacro) {
- invoke((ASTDirMacro) macroOrDirective, null, null, null, null);
- } else if (macroOrDirective instanceof TemplateDirectiveModel) {
- ((TemplateDirectiveModel) macroOrDirective).execute(
- null, null /* TODO [FM3][CF] */, out, this);
- } else {
+ TemplateDirectiveModel nodeProcessor = getNodeProcessor(node);
+ if (nodeProcessor != null) {
+ _TemplateCallableModelUtils.executeWith0Arguments(
+ nodeProcessor, NonTemplateCallPlace.INSTANCE, out, this);
+ } else if (nodeProcessor == null) {
String nodeType = node.getNodeType();
if (nodeType != null) {
+ // TODO [FM3] We are supposed to be o.a.f.dom unaware in the core, plus these types can mean
+ // something else with another wrapper. So we should encode the default behavior into the
+ // // TemplateNodeModel somehow.
+
// If the node's type is 'text', we just output it.
if ((nodeType.equals("text") && node instanceof TemplateScalarModel)) {
- out.write(((TemplateScalarModel) node).getAsString());
+ out.write(((TemplateScalarModel) node).getAsString()); // TODO [FM3] Escaping?
} else if (nodeType.equals("document")) {
recurse(node, namespaces);
}
@@ -637,134 +638,23 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
}
void fallback() throws TemplateException, IOException {
- TemplateModel macroOrDirective = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
- if (macroOrDirective instanceof ASTDirMacro) {
- invoke((ASTDirMacro) macroOrDirective, null, null, null, null);
- } else if (macroOrDirective instanceof TemplateDirectiveModel) {
- ((TemplateDirectiveModel) macroOrDirective).execute(
- null, null /* TODO [FM3][CF] */, out, this);
- }
- }
-
- /**
- * Calls the macro or function with the given arguments and nested block.
- */
- void invoke(ASTDirMacro macro,
- Map<String, ASTExpression> namedArgs, List<ASTExpression> positionalArgs,
- List<String> bodyParameterNames, ASTElement[] childBuffer) throws TemplateException, IOException {
- if (macro == ASTDirMacro.DO_NOTHING_MACRO) {
- return;
- }
-
- pushElement(macro);
- try {
- final ASTDirMacro.Context macroCtx = macro.new Context(this, childBuffer, bodyParameterNames);
- setMacroContextLocalsFromArguments(macroCtx, macro, namedArgs, positionalArgs);
-
- final ASTDirMacro.Context prevMacroCtx = currentMacroContext;
- currentMacroContext = macroCtx;
-
- final LocalContextStack prevLocalContextStack = localContextStack;
- localContextStack = null;
-
- final Namespace prevNamespace = currentNamespace;
- currentNamespace = (Namespace) macroToNamespaceLookup.get(macro);
-
- try {
- macroCtx.sanityCheck(this);
- visit(macro.getChildBuffer());
- } catch (ASTDirReturn.Return re) {
- // Not an error, just a <#return>
- } catch (TemplateException te) {
- handleTemplateException(te);
- } finally {
- currentMacroContext = prevMacroCtx;
- localContextStack = prevLocalContextStack;
- currentNamespace = prevNamespace;
- }
- } finally {
- popElement();
- }
- }
-
- /**
- * Sets the local variables corresponding to the macro call arguments in the macro context.
- */
- private void setMacroContextLocalsFromArguments(
- final ASTDirMacro.Context macroCtx,
- final ASTDirMacro macro,
- final Map<String, ASTExpression> namedArgs, final List<ASTExpression> positionalArgs) throws TemplateException {
- String catchAllParamName = macro.getCatchAll();
- if (namedArgs != null) {
- final NativeHashEx2 catchAllParamValue;
- if (catchAllParamName != null) {
- catchAllParamValue = new NativeHashEx2();
- macroCtx.setLocalVar(catchAllParamName, catchAllParamValue);
- } else {
- catchAllParamValue = null;
- }
-
- for (Map.Entry<String, ASTExpression> argNameAndValExp : namedArgs.entrySet()) {
- final String argName = argNameAndValExp.getKey();
- final boolean isArgNameDeclared = macro.hasArgNamed(argName);
- if (isArgNameDeclared || catchAllParamName != null) {
- ASTExpression argValueExp = argNameAndValExp.getValue();
- TemplateModel argValue = argValueExp.eval(this);
- if (isArgNameDeclared) {
- macroCtx.setLocalVar(argName, argValue);
- } else {
- catchAllParamValue.put(argName, argValue);
- }
- } else {
- throw new _MiscTemplateException(this,
- (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
- " has no parameter with name ", new _DelayedJQuote(argName), ".");
- }
- }
- } else if (positionalArgs != null) {
- final NativeSequence catchAllParamValue;
- if (catchAllParamName != null) {
- catchAllParamValue = new NativeSequence(8);
- macroCtx.setLocalVar(catchAllParamName, catchAllParamValue);
- } else {
- catchAllParamValue = null;
- }
-
- String[] argNames = macro.getArgumentNamesInternal();
- final int argsCnt = positionalArgs.size();
- if (argNames.length < argsCnt && catchAllParamName == null) {
- throw new _MiscTemplateException(this,
- (macro.isFunction() ? "Function " : "Macro "), new _DelayedJQuote(macro.getName()),
- " only accepts ", new _DelayedToString(argNames.length), " parameters, but got ",
- new _DelayedToString(argsCnt), ".");
- }
- for (int i = 0; i < argsCnt; i++) {
- ASTExpression argValueExp = positionalArgs.get(i);
- TemplateModel argValue = argValueExp.eval(this);
- try {
- if (i < argNames.length) {
- String argName = argNames[i];
- macroCtx.setLocalVar(argName, argValue);
- } else {
- catchAllParamValue.add(argValue);
- }
- } catch (RuntimeException re) {
- throw new _MiscTemplateException(re, this);
- }
- }
+ TemplateDirectiveModel nodeProcessor = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
+ if (nodeProcessor != null) {
+ _TemplateCallableModelUtils.executeWith0Arguments(
+ nodeProcessor, NonTemplateCallPlace.INSTANCE, out, this);
}
}
/**
* Defines the given macro in the current namespace (doesn't call it).
*/
- void visitMacroDef(ASTDirMacro macro) {
- macroToNamespaceLookup.put(macro, currentNamespace);
- currentNamespace.put(macro.getName(), macro);
- }
-
- Namespace getMacroNamespace(ASTDirMacro macro) {
- return (Namespace) macroToNamespaceLookup.get(macro);
+ void visitMacroOrFunctionDefinition(ASTDirMacroOrFunction definition) {
+ Namespace currentNamespace = this.getCurrentNamespace();
+ currentNamespace.put(
+ definition.getName(),
+ definition.isFunction()
+ ? new TemplateLanguageFunction(definition, currentNamespace)
+ : new TemplateLanguageDirective(definition, currentNamespace));
}
void recurse(TemplateNodeModel node, TemplateSequenceModel namespaces)
@@ -786,7 +676,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
}
}
- ASTDirMacro.Context getCurrentMacroContext() {
+ ASTDirMacroOrFunction.Context getCurrentMacroContext() {
return currentMacroContext;
}
@@ -937,7 +827,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
* Tells if the same concrete time zone is used for SQL date-only and time-only values as for other
* date/time/date-time values.
*/
- boolean isSQLDateAndTimeTimeZoneSameAsNormal() {
+ private boolean isSQLDateAndTimeTimeZoneSameAsNormal() {
if (cachedSQLDateAndTimeTimeZoneSameAsNormal == null) {
cachedSQLDateAndTimeTimeZoneSameAsNormal = Boolean.valueOf(
getSQLDateAndTimeTimeZone() == null
@@ -1959,7 +1849,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
* (Note that the misnomer is kept for backward compatibility: nested content parameters are not local variables
* according to our terminology.)
*/
- // TODO [FM3] Don't return nested content params anymore (see JavaDoc)? But, LocalContext does return them...
+ // TODO [FM3] Don't return nested content params anymore (see JavaDoc)
public TemplateModel getLocalVariable(String name) throws TemplateModelException {
if (localContextStack != null) {
for (int i = localContextStack.size() - 1; i >= 0; i--) {
@@ -2227,7 +2117,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
sb.append(MessageUtil.shorten(stackEl.getDescription(), 40));
sb.append(" [");
- ASTDirMacro enclosingMacro = getEnclosingMacro(stackEl);
+ ASTDirMacroOrFunction enclosingMacro = getEnclosingMacro(stackEl);
if (enclosingMacro != null) {
sb.append(MessageUtil.formatLocationForEvaluationError(
enclosingMacro, stackEl.beginLine, stackEl.beginColumn));
@@ -2238,9 +2128,9 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
sb.append("]");
}
- static private ASTDirMacro getEnclosingMacro(ASTElement stackEl) {
+ static private ASTDirMacroOrFunction getEnclosingMacro(ASTElement stackEl) {
while (stackEl != null) {
- if (stackEl instanceof ASTDirMacro) return (ASTDirMacro) stackEl;
+ if (stackEl instanceof ASTDirMacroOrFunction) return (ASTDirMacroOrFunction) stackEl;
stackEl = stackEl.getParent();
}
return null;
@@ -2420,12 +2310,12 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
currentVisitorNode = node;
}
- TemplateModel getNodeProcessor(TemplateNodeModel node) throws TemplateException {
+ TemplateDirectiveModel getNodeProcessor(TemplateNodeModel node) throws TemplateException {
String nodeName = node.getNodeName();
if (nodeName == null) {
- throw new _MiscTemplateException(this, "Node name is null.");
+ throw new _MiscTemplateException(this, "Node name was null.");
}
- TemplateModel result = getNodeProcessor(nodeName, node.getNodeNamespace(), 0);
+ TemplateDirectiveModel result = getNodeProcessor(nodeName, node.getNodeNamespace(), 0);
if (result == null) {
String type = node.getNodeType();
@@ -2445,9 +2335,9 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
return result;
}
- private TemplateModel getNodeProcessor(final String nodeName, final String nsURI, int startIndex)
+ private TemplateDirectiveModel getNodeProcessor(final String nodeName, final String nsURI, int startIndex)
throws TemplateException {
- TemplateModel result = null;
+ TemplateDirectiveModel result = null;
int i;
for (i = startIndex; i < nodeNamespaces.size(); i++) {
Namespace ns = null;
@@ -2470,11 +2360,12 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
return result;
}
- private TemplateModel getNodeProcessor(Namespace ns, String localName, String nsURI) throws TemplateException {
+ private TemplateDirectiveModel getNodeProcessor(Namespace ns, String localName, String nsURI)
+ throws TemplateException {
TemplateModel result = null;
if (nsURI == null) {
result = ns.get(localName);
- if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+ if (!(result instanceof TemplateDirectiveModel)) {
result = null;
}
} else {
@@ -2487,31 +2378,31 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
}
if (prefix.length() > 0) {
result = ns.get(prefix + ":" + localName);
- if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+ if (!(result instanceof TemplateDirectiveModel)) {
result = null;
}
} else {
- if (nsURI.length() == 0) {
+ if (nsURI.isEmpty()) {
result = ns.get(Template.NO_NS_PREFIX + ":" + localName);
- if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+ if (!(result instanceof TemplateDirectiveModel)) {
result = null;
}
}
if (nsURI.equals(template.getDefaultNS())) {
result = ns.get(Template.DEFAULT_NAMESPACE_PREFIX + ":" + localName);
- if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+ if (!(result instanceof TemplateDirectiveModel)) {
result = null;
}
}
if (result == null) {
result = ns.get(localName);
- if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
+ if (!(result instanceof TemplateDirectiveModel)) {
result = null;
}
}
}
}
- return result;
+ return (TemplateDirectiveModel) result;
}
/**
@@ -2759,7 +2650,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
void importMacros(Template template) {
for (Object macro : template.getMacros().values()) {
- visitMacroDef((ASTDirMacro) macro);
+ visitMacroOrFunctionDefinition((ASTDirMacroOrFunction) macro);
}
}
@@ -3034,4 +2925,158 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
return res;
}
+ /**
+ * Superclass of {@link TemplateCallableModel}-s implemented in the template language.
+ */
+ abstract class TemplateLanguageCallable implements TemplateCallableModel {
+ private final ASTDirMacroOrFunction callableDefinition;
+ private final Namespace namespace;
+
+ public TemplateLanguageCallable(ASTDirMacroOrFunction callableDefinition, Namespace namespace) {
+ this.callableDefinition = callableDefinition;
+ this.namespace = namespace;
+ }
+
+ protected void genericExecute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+ throws TemplateException, IOException {
+ pushElement(callableDefinition);
+ try {
+ final ASTDirMacroOrFunction.Context macroCtx = callableDefinition.new Context(
+ this, callPlace, Environment.this);
+
+ final ASTDirMacroOrFunction.Context prevMacroCtx = currentMacroContext;
+ currentMacroContext = macroCtx;
+
+ final LocalContextStack prevLocalContextStack = localContextStack;
+ localContextStack = null;
+
+ final Namespace prevNamespace = currentNamespace;
+ currentNamespace = namespace;
+
+ try {
+ // Note: Default expressions are evaluated here, so namespace, stack, etc. must be already set
+ setLocalsFromArguments(macroCtx, args);
+
+ visit(callableDefinition.getChildBuffer(), out);
+ } catch (ASTDirReturn.Return re) {
+ // Not an error, just a <#return>
+ } catch (TemplateException te) {
+ handleTemplateException(te);
+ } finally {
+ currentMacroContext = prevMacroCtx;
+ localContextStack = prevLocalContextStack;
+ currentNamespace = prevNamespace;
+ }
+ } finally {
+ popElement();
+ }
+ }
+
+ private void setLocalsFromArguments(ASTDirMacroOrFunction.Context macroCtx, TemplateModel[] args)
+ throws TemplateException {
+ ASTDirMacroOrFunction.ParameterDefinition[] paramDefsByArgIdx =
+ callableDefinition.getParameterDefinitionByArgumentArrayIndex();
+ for (int argIdx = 0; argIdx < args.length; argIdx++) {
+ TemplateModel arg = args[argIdx];
+ ASTDirMacroOrFunction.ParameterDefinition paramDef = paramDefsByArgIdx[argIdx];
+ if (arg == null) { // TODO [FM3] FM2 doesn't differentiate omitted and null, but FM3 will.
+ ASTExpression defaultExp = paramDef.getDefaultExpression();
+ if (defaultExp != null) {
+ arg = defaultExp.eval(Environment.this);
+ if (arg == null) {
+ throw InvalidReferenceException.getInstance(defaultExp, Environment.this);
+ }
+ } else {
+ // TODO [FM3] Had to give different messages depending on if the argument was omitted, or if
+ // it was null, but this will be fixed with the null related refactoring.
+ throw new _MiscTemplateException(Environment.this,
+ new _ErrorDescriptionBuilder(
+ "When calling macro ", new _DelayedJQuote(callableDefinition.getName()),
+ ", required parameter ", new _DelayedJQuote(paramDef.getName()),
+ (argIdx < getArgumentArrayLayout().getPredefinedPositionalArgumentCount()
+ ? new Object[] { " (parameter #", (argIdx + 1), ")" }
+ : ""),
+ " was either not specified, or had null/missing value.")
+ .tip("If the parameter value expression on the caller side is known to "
+ + "be legally null/missing, you may want to specify a default "
+ + "value for it on the caller side with the \"!\" operator, like "
+ + "paramValue!defaultValue.")
+ .tip("If the parameter was omitted on the caller side, and the omission was "
+ + "deliberate, you may consider making the parameter optional in the macro "
+ + "by specifying a default value for it, like <#macro macroName "
+ + "paramName=defaultExpr>."
+ )
+ );
+ }
+ }
+ macroCtx.setLocalVar(paramDef.getName(), arg);
+ }
+ }
+
+ @Override
+ public ArgumentArrayLayout getArgumentArrayLayout() {
+ return callableDefinition.getArgumentArrayLayout();
+ }
+
+ ASTDirMacroOrFunction getCallableDefinition() {
+ return callableDefinition;
+ }
+
+ Namespace getNamespace() {
+ return namespace;
+ }
+ }
+
+ /**
+ * {@link TemplateDirectiveModel} implemented in the template language (such as with the {@code #macro} directive).
+ * This is the value that {@code #macro} creates on runtime, not the {@code #macro} directive itself.
+ */
+ final class TemplateLanguageDirective extends TemplateLanguageCallable implements TemplateDirectiveModel {
+
+ public TemplateLanguageDirective(ASTDirMacroOrFunction macroDef, Namespace namespace) {
+ super(macroDef, namespace);
+ }
+
+ @Override
+ public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env)
+ throws IOException, TemplateException {
+ if (getCallableDefinition() == ASTDirMacroOrFunction.PASS_MACRO) {
+ return;
+ }
+ genericExecute(args, callPlace, out, env);
+ }
+
+ @Override
+ public boolean isNestedContentSupported() {
+ // TODO [FM3] We should detect if #nested is called anywhere (also maybe something like
+ // `<#macro m{supportsNested[=true|false]}>`) should be added.
+ return true;
+ }
+
+ }
+
+ /**
+ * {@link TemplateFunctionModel} implemented in the template language (such as with the {@code #function}
+ * directive). This is the value that {@code #function} creates on runtime, not the {@code #macro} directive itself.
+ */
+ final class TemplateLanguageFunction extends TemplateLanguageCallable implements TemplateFunctionModel {
+
+ public TemplateLanguageFunction(ASTDirMacroOrFunction callableDefinition, Namespace namespace) {
+ super(callableDefinition, namespace);
+ }
+
+ @Override
+ public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
+ throws TemplateException {
+ env.setLastReturnValue(null);
+ try {
+ genericExecute(args, callPlace, _NullWriter.INSTANCE, env);
+ } catch (IOException e) {
+ // Should not occur
+ throw new TemplateException("Unexpected exception during function execution", e, env);
+ }
+ return env.getLastReturnValue();
+ }
+ }
+
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
index 42c5455..59d867d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/LocalContext.java
@@ -26,7 +26,7 @@ import org.apache.freemarker.core.model.TemplateModelException;
/**
* An interface that represents a local context. This is used as the abstraction for
- * the context of a ASTDirMacro invocation, a loop, or the nested block call from within
+ * the context of a ASTDirMacroOrFunction invocation, a loop, or the nested block call from within
* a macro.
*/
public interface LocalContext {
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
index 8820f08..a1858b1 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtil.java
@@ -71,7 +71,7 @@ class MessageUtil {
return formatLocation("at", template, line, column);
}
- static String formatLocationForEvaluationError(ASTDirMacro macro, int line, int column) {
+ static String formatLocationForEvaluationError(ASTDirMacroOrFunction macro, int line, int column) {
Template t = macro.getTemplate();
return formatLocation("at", t != null ? t.getSourceOrLookupName() : null, macro.getName(), macro.isFunction(),
line, column);
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.java
new file mode 100644
index 0000000..2053361
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NonDirectiveException.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Indicates that a {@link TemplateDirectiveModel} was expected, but the value had a different type.
+ */
+class NonDirectiveException extends UnexpectedTypeException {
+
+ private static final Class[] EXPECTED_TYPES = new Class[] {
+ TemplateDirectiveModel.class, ASTDirMacroOrFunction.class };
+
+ public NonDirectiveException(Environment env) {
+ super(env, "Expecting user-defined directive, transform or macro value here");
+ }
+
+ public NonDirectiveException(String description, Environment env) {
+ super(env, description);
+ }
+
+ NonDirectiveException(Environment env, _ErrorDescriptionBuilder description) {
+ super(env, description);
+ }
+
+ NonDirectiveException(
+ ASTExpression blamed, TemplateModel model, Environment env)
+ throws InvalidReferenceException {
+ super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, env);
+ }
+
+ NonDirectiveException(
+ ASTExpression blamed, TemplateModel model, String tip,
+ Environment env)
+ throws InvalidReferenceException {
+ super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tip, env);
+ }
+
+ NonDirectiveException(
+ ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+ super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tips, env);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java
new file mode 100644
index 0000000..917ccbf
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NonTemplateCallPlace.java
@@ -0,0 +1,166 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.util.CommonSupplier;
+
+/**
+ * {@link CallPlace} used when a {@link TemplateDirectiveModel} is called from outside a template (such as from user
+ * Java code).
+ */
+public final class NonTemplateCallPlace implements CallPlace {
+
+ public static final NonTemplateCallPlace INSTANCE = new NonTemplateCallPlace();
+
+ private final int firstTargetJavaParameterTypeIndex;
+ private final Class<?>[] targetJavaParameterTypes;
+
+ private NonTemplateCallPlace() {
+ this(-1, null);
+ }
+
+ /**
+ * @param firstTargetJavaParameterTypeIndex
+ * See {@link CallPlace#getFirstTargetJavaParameterTypeIndex()}
+ * @param targetJavaParameterTypes
+ * The target Java types of the arguments of the invocation, starting with the target Java type of the
+ * argument at index {@code firstTargetJavaParameterTypeIndex}, and usually ending with the target type of
+ * the last argument that has a non-{@code null} target type (although having {@code null}-s at the end is
+ * legal too). For example, using Java-like call syntax, if the call is like
+ * {@code m(a, b, (Foo) c, d, (Bar) e, f)}, then {@code firstTargetJavaParameterTypeIndex} is 2,
+ * and the array will be {@code new Class<?>[] { } [ Foo.class, null, Bar.class ]}.
+ */
+ public NonTemplateCallPlace(int firstTargetJavaParameterTypeIndex, Class<?>[] targetJavaParameterTypes) {
+ this.firstTargetJavaParameterTypeIndex = firstTargetJavaParameterTypeIndex;
+ this.targetJavaParameterTypes = targetJavaParameterTypes;
+ }
+
+ /**
+ * Always returns {@code false}.
+ */
+ @Override
+ public boolean hasNestedContent() {
+ return false;
+ }
+
+ /**
+ * Always returns {@code 0}.
+ */
+ @Override
+ public int getNestedContentParameterCount() {
+ return 0;
+ }
+
+ /**
+ * Does nothing.
+ */
+ @Override
+ public void executeNestedContent(TemplateModel[] nestedContentArgs, Writer out, Environment env)
+ throws TemplateException, IOException {
+ // Do nothing
+ }
+
+ /**
+ * Always returns {@code -1}.
+ */
+ @Override
+ public Template getTemplate() {
+ return null;
+ }
+
+ /**
+ * Always returns {@code -1}.
+ */
+ @Override
+ public int getBeginColumn() {
+ return -1;
+ }
+
+ /**
+ * Always returns {@code -1}.
+ */
+ @Override
+ public int getBeginLine() {
+ return -1;
+ }
+
+ /**
+ * Always returns {@code -1}.
+ */
+ @Override
+ public int getEndColumn() {
+ return -1;
+ }
+
+ /**
+ * Always returns {@code -1}.
+ */
+ @Override
+ public int getEndLine() {
+ return -1;
+ }
+
+ /**
+ * Always throws {@link UnsupportedOperationException}.
+ */
+ @Override
+ public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
+ throws CallPlaceCustomDataInitializationException {
+ throw new UnsupportedOperationException("Non-template call place doesn't support custom data storage");
+ }
+
+ /**
+ * Always returns {@code false}.
+ */
+ @Override
+ public boolean isCustomDataSupported() {
+ return false;
+ }
+
+ /**
+ * Always returns {@code false}.
+ */
+ @Override
+ public boolean isNestedOutputCacheable() {
+ return false;
+ }
+
+ /**
+ * Always returns {@code -1}.
+ */
+ @Override
+ public int getFirstTargetJavaParameterTypeIndex() {
+ return firstTargetJavaParameterTypeIndex;
+ }
+
+ /**
+ * Always returns {@code null}.
+ */
+ @Override
+ public Class<?> getTargetJavaParameterType(int argIndex) {
+ int idx = argIndex - firstTargetJavaParameterTypeIndex;
+ return idx >= 0 || idx < targetJavaParameterTypes.length ? targetJavaParameterTypes[idx] : null;
+ }
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
deleted file mode 100644
index 547d688..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
+++ /dev/null
@@ -1,65 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import org.apache.freemarker.core.model.TemplateDirectiveModel;
-import org.apache.freemarker.core.model.TemplateModel;
-
-// TODO [FM3][CF] Review and rename this when TDM2 and TFM are in place
-/**
- * Indicates that a {@link TemplateDirectiveModel} or {@link ASTDirMacro} value was
- * expected, but the value had a different type.
- */
-class NonUserDefinedDirectiveLikeException extends UnexpectedTypeException {
-
- private static final Class[] EXPECTED_TYPES = new Class[] {
- TemplateDirectiveModel.class, ASTDirMacro.class };
-
- public NonUserDefinedDirectiveLikeException(Environment env) {
- super(env, "Expecting user-defined directive, transform or macro value here");
- }
-
- public NonUserDefinedDirectiveLikeException(String description, Environment env) {
- super(env, description);
- }
-
- NonUserDefinedDirectiveLikeException(Environment env, _ErrorDescriptionBuilder description) {
- super(env, description);
- }
-
- NonUserDefinedDirectiveLikeException(
- ASTExpression blamed, TemplateModel model, Environment env)
- throws InvalidReferenceException {
- super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, env);
- }
-
- NonUserDefinedDirectiveLikeException(
- ASTExpression blamed, TemplateModel model, String tip,
- Environment env)
- throws InvalidReferenceException {
- super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tip, env);
- }
-
- NonUserDefinedDirectiveLikeException(
- ASTExpression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
- super(blamed, model, "user-defined directive, transform or macro", EXPECTED_TYPES, tips, env);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
index ccae7b7..b9ce370 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParameterRole.java
@@ -54,15 +54,11 @@ final class ParameterRole {
static final ParameterRole TEMPLATE_NAME = new ParameterRole("template name");
static final ParameterRole IGNORE_MISSING_PARAMETER = new ParameterRole("\"ignore_missing\" parameter");
static final ParameterRole PARAMETER_NAME = new ParameterRole("parameter name");
- static final ParameterRole PARAMETER_DEFAULT = new ParameterRole("parameter default");
- static final ParameterRole CATCH_ALL_PARAMETER_NAME = new ParameterRole("catch-all parameter name");
+ static final ParameterRole PARAMETER_DEFINITION = new ParameterRole("parameter definition");
static final ParameterRole ARGUMENT_NAME = new ParameterRole("argument name");
static final ParameterRole ARGUMENT_VALUE = new ParameterRole("argument value");
static final ParameterRole CONTENT = new ParameterRole("content");
- static final ParameterRole EMBEDDED_TEMPLATE = new ParameterRole("embedded template");
static final ParameterRole VALUE_PART = new ParameterRole("value part");
- static final ParameterRole MINIMUM_DECIMALS = new ParameterRole("minimum decimals");
- static final ParameterRole MAXIMUM_DECIMALS = new ParameterRole("maximum decimals");
static final ParameterRole NODE = new ParameterRole("node");
static final ParameterRole CALLEE = new ParameterRole("callee");
static final ParameterRole MESSAGE = new ParameterRole("message");
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
index 5ea4517..fde11d9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Template.java
@@ -735,7 +735,7 @@ public class Template implements ProcessingConfiguration, CustomStateScope {
out.write(rootElement != null ? rootElement.getCanonicalForm() : "Unfinished template");
}
- void addMacro(ASTDirMacro macro) {
+ void addMacro(ASTDirMacroOrFunction macro) {
macros.put(macro.getName(), macro);
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
deleted file mode 100644
index 8313d53..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
+++ /dev/null
@@ -1,46 +0,0 @@
-package org.apache.freemarker.core;
-
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-
-public class TemplateCallableModelUtils {
-
- // TODO [FM3][CF] Add this to the other exception classes too
- public static TemplateNumberModel castArgumentToNumber(TemplateModel[] args, int argIndex, boolean allowNull,
- Environment env) throws TemplateException {
- return getTemplateNumberModel(args[argIndex], argIndex, allowNull, env);
- }
-
- // TODO [FM3][CF] Add this to the other exception classes too
- private static TemplateNumberModel getTemplateNumberModel(TemplateModel argValue, int argIndex, boolean allowNull,
- Environment env) throws TemplateException {
- if (argValue instanceof TemplateNumberModel) {
- return (TemplateNumberModel) argValue;
- }
- if (argValue == null) {
- if (allowNull) {
- return null;
- }
- throw new _MiscTemplateException(env,
- "The ", new _DelayedOrdinal(argIndex + 1), " argument can't be null.");
- }
- throw new NonNumericalException(argIndex, argValue, null, env);
- }
-
- // TODO [FM3][CF] Add this to the other exception classes too
- public static TemplateNumberModel castArgumentToNumber(TemplateModel argValue, String argName, boolean allowNull,
- Environment env) throws TemplateException {
- if (argValue instanceof TemplateNumberModel) {
- return (TemplateNumberModel) argValue;
- }
- if (argValue == null) {
- if (allowNull) {
- return null;
- }
- throw new _MiscTemplateException(env,
- "The ", new _DelayedJQuote(argName), " argument can't be null.");
- }
- throw new NonNumericalException(argName, argValue, null, env);
- }
-
-}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
index 71db276..a1b60bf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
@@ -21,7 +21,6 @@ package org.apache.freemarker.core;
import java.io.IOException;
-import org.apache.freemarker.core.model.CallPlace;
import org.apache.freemarker.core.model.TemplateDateModel;
/**
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/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 b575901..c904afa 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
@@ -61,14 +61,12 @@ public final class _CoreAPI {
}
}
- // [FM3] Should become unnecessary as custom directive classes are reworked
- public static boolean isMacroOrFunction(TemplateModel m) {
- return m instanceof ASTDirMacro;
+ public static boolean isMacro(Class<? extends TemplateModel> cl) {
+ return Environment.TemplateLanguageDirective.class.isAssignableFrom(cl);
}
- // [FM3] Should become unnecessary as custom directive classes are reworked
- public static boolean isFunction(TemplateModel m) {
- return m instanceof ASTDirMacro && ((ASTDirMacro) m).isFunction();
+ public static boolean isFunction(Class<? extends TemplateModel> cl) {
+ return Environment.TemplateLanguageFunction.class.isAssignableFrom(cl);
}
public static void checkVersionNotNullAndSupported(Version incompatibleImprovements) {
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
index c548487..13cadcd 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_ErrorDescriptionBuilder.java
@@ -211,6 +211,8 @@ public class _ErrorDescriptionBuilder {
for (Object partObj : parts) {
if (partObj instanceof Object[]) {
appendParts(sb, (Object[]) partObj);
+ } else if (partObj instanceof Class<?>) {
+ sb.append(((Class<?>) partObj).getName());
} else {
String partStr;
partStr = tryToString(partObj);
@@ -219,12 +221,13 @@ public class _ErrorDescriptionBuilder {
}
if (template != null) {
+ // Translate tag syntax of the part looks like an FTL tag
if (partStr.length() > 4
&& partStr.charAt(0) == '<'
&& (
(partStr.charAt(1) == '#' || partStr.charAt(1) == '@')
|| (partStr.charAt(1) == '/') && (partStr.charAt(2) == '#' || partStr.charAt(2) == '@')
- )
+ )
&& partStr.charAt(partStr.length() - 1) == '>') {
if (template.getActualTagSyntax() == TagSyntax.SQUARE_BRACKET) {
sb.append('[');
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3cacd9ed/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java
new file mode 100644
index 0000000..277dc1a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_TemplateCallableModelUtils.java
@@ -0,0 +1,112 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.Constants;
+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;
+
+/**
+ * For internal use only; don't depend on this, there's no backward compatibility guarantee at all!
+ */
+// TODO [FM3] Most functionality here should be made public on some way
+public final class _TemplateCallableModelUtils {
+
+ private _TemplateCallableModelUtils() {
+ //
+ }
+
+ public static void executeWith0Arguments(
+ TemplateDirectiveModel directive, CallPlace callPlace, Writer out, Environment env)
+ throws IOException, TemplateException {
+ directive.execute(getArgumentArrayWithNoArguments(directive), callPlace, out, env);
+ }
+
+ public static TemplateModel executeWith0Arguments(
+ TemplateFunctionModel function, CallPlace callPlace, Environment env)
+ throws TemplateException {
+ return function.execute(getArgumentArrayWithNoArguments(function), callPlace, env);
+ }
+
+ private static TemplateModel[] getArgumentArrayWithNoArguments(TemplateCallableModel callable) {
+ ArgumentArrayLayout argsLayout = callable.getArgumentArrayLayout();
+ int totalLength = argsLayout.getTotalLength();
+ if (totalLength == 0) {
+ return Constants.EMPTY_TEMPLATE_MODEL_ARRAY;
+ } else {
+ TemplateModel[] args = new TemplateModel[totalLength];
+
+ int positionalVarargsArgumentIndex = argsLayout.getPositionalVarargsArgumentIndex();
+ if (positionalVarargsArgumentIndex != -1) {
+ args[positionalVarargsArgumentIndex] = Constants.EMPTY_SEQUENCE;
+ }
+
+ int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
+ if (namedVarargsArgumentIndex != -1) {
+ args[namedVarargsArgumentIndex] = Constants.EMPTY_SEQUENCE;
+ }
+
+ return args;
+ }
+ }
+
+ public static TemplateNumberModel castArgumentToNumber(TemplateModel[] args, int argIndex, boolean allowNull,
+ Environment env) throws TemplateException {
+ return getTemplateNumberModel(args[argIndex], argIndex, allowNull, env);
+ }
+
+ private static TemplateNumberModel getTemplateNumberModel(TemplateModel argValue, int argIndex, boolean allowNull,
+ Environment env) throws TemplateException {
+ if (argValue instanceof TemplateNumberModel) {
+ return (TemplateNumberModel) argValue;
+ }
+ if (argValue == null) {
+ if (allowNull) {
+ return null;
+ }
+ throw new _MiscTemplateException(env,
+ "The ", new _DelayedOrdinal(argIndex + 1), " argument can't be null.");
+ }
+ throw new NonNumericalException(argIndex, argValue, null, env);
+ }
+
+ public static TemplateNumberModel castArgumentToNumber(TemplateModel argValue, String argName, boolean allowNull,
+ Environment env) throws TemplateException {
+ if (argValue instanceof TemplateNumberModel) {
+ return (TemplateNumberModel) argValue;
+ }
+ if (argValue == null) {
+ if (allowNull) {
+ return null;
+ }
+ throw new _MiscTemplateException(env,
+ "The ", new _DelayedJQuote(argName), " argument can't be null.");
+ }
+ throw new NonNumericalException(argName, argValue, null, env);
+ }
+
+}