FREEMARKER-63: Very early state. Some cleanups. Temporarily added support for calling legacy ASTDirMacro-s with the also temporary <~...>syntax.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/fbbfadb4 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/fbbfadb4 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/fbbfadb4 Branch: refs/heads/3 Commit: fbbfadb443efac2009439564f1d3c664ff8a51b0 Parents: 46c7501 Author: ddekany <ddek...@apache.org> Authored: Thu Jul 27 18:20:37 2017 +0200 Committer: ddekany <ddek...@apache.org> Committed: Thu Jul 27 18:20:37 2017 +0200 ---------------------------------------------------------------------- .../core/TemplateCallableModelTest.java | 23 +++++++ .../core/userpkg/AllFeaturesDirective.java | 4 +- .../core/userpkg/TwoNamedParamsDirective.java | 2 +- .../userpkg/TwoPositionalParamsDirective.java | 2 +- .../core/userpkg/UpperCaseDirective.java | 71 ++++++++++++++++++++ .../freemarker/core/ASTDirDynamicCall.java | 35 +++++++++- .../org/apache/freemarker/core/ASTDirList.java | 2 +- .../org/apache/freemarker/core/ASTDirMacro.java | 10 +-- .../apache/freemarker/core/ASTDirNested.java | 10 +-- .../freemarker/core/ASTDirUserDefined.java | 1 + .../org/apache/freemarker/core/Environment.java | 60 ++++++++++------- .../apache/freemarker/core/LocalContext.java | 3 +- .../apache/freemarker/core/model/CallPlace.java | 5 +- .../core/model/TemplateCallableModel.java | 24 ++++--- .../core/model/TemplateDirectiveModel2.java | 27 +++++--- .../freemarker/core/util/StringToIndexMap.java | 2 +- .../freemarker/core/util/_ArrayAdapterList.java | 62 +++++++++++++++++ .../apache/freemarker/core/util/_ArrayList.java | 58 ---------------- 18 files changed, 277 insertions(+), 124 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java index 536727a..f5c1b61 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/TemplateCallableModelTest.java @@ -24,6 +24,7 @@ import java.io.IOException; import org.apache.freemarker.core.userpkg.AllFeaturesDirective; import org.apache.freemarker.core.userpkg.TwoNamedParamsDirective; import org.apache.freemarker.core.userpkg.TwoPositionalParamsDirective; +import org.apache.freemarker.core.userpkg.UpperCaseDirective; import org.apache.freemarker.test.TemplateTest; import org.junit.Before; import org.junit.Test; @@ -143,4 +144,26 @@ public class TemplateCallableModelTest extends TemplateTest { assertErrorContains("<~p n1=9 />", "doesn't have any by-name-passed"); } + @Test + public void testMacros() throws IOException, TemplateException { + assertOutput("<#macro m a b=22><#list 1..2 as n>[<#nested a * n, b * n>]</#list></#macro>" + + "<~m 11; i, j>${i} ${j}</~m> <~m a=1 b=2; i, j>${i} ${j}</~m>", + "[11 22][22 44] [1 2][2 4]"); + assertOutput("<#macro m a b others...>[a=${a}, b=${b}<#if others?hasContent>, </#if>" + + "<#if others?isSequence>" + + "<#list others as v>${v}<#sep>, </#list>" + + "<#else>" + + "<#list others as k, v>${k}=${v}<#sep>, </#list>" + + "</#if>]" + + "</#macro>" + + "<~m 1, 2 /> <~m 1, 2, 3, 4 /> <~m a=1 b=2 /> <~m a=1 b=2 c=3 d=4 />", + "[a=1, b=2] [a=1, b=2, 3, 4] [a=1, b=2] [a=1, b=2, c=3, d=4]"); + } + + @Test + public void testFilterDirective() throws IOException, TemplateException { + addToDataModel("uc", new UpperCaseDirective()); + assertOutput("<~uc>foo ${1 + 1}</~>", "FOO 2"); + } + } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java index 91e6d2a..59f084d 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/AllFeaturesDirective.java @@ -68,7 +68,7 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel { N2_ARG_NAME, N2_ARG_IDX); @Override - public void execute(TemplateModel[] args, Writer out, Environment env, CallPlace callPlace) + public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) throws TemplateException, IOException { execute(castArgumentToNumber(args, P1_ARG_IDX, p1AllowNull, env), castArgumentToNumber(args, P2_ARG_IDX, p2AllowNull, env), @@ -104,7 +104,7 @@ public class AllFeaturesDirective extends TestTemplateDirectiveModel { for (int loopVarIdx = 0; loopVarIdx < loopVariableCount; loopVarIdx++) { loopVariableValues[loopVarIdx] = new SimpleNumber((i + 1) * (loopVarIdx + 1)); } - callPlace.executeNestedContent(loopVariableValues, env); + callPlace.executeNestedContent(loopVariableValues, out, env); } } out.write("}"); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java index 8c58853..2978bf1 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoNamedParamsDirective.java @@ -41,7 +41,7 @@ public class TwoNamedParamsDirective extends TestTemplateDirectiveModel { N2_ARG_NAME, N2_ARG_IDX); @Override - public void execute(TemplateModel[] args, Writer out, Environment env, CallPlace callPlace) + public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) throws TemplateException, IOException { out.write("#n("); printParam(N1_ARG_NAME, args[N1_ARG_IDX], out, true); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java index e9eb926..3603069 100644 --- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TwoPositionalParamsDirective.java @@ -31,7 +31,7 @@ import org.apache.freemarker.core.model.TemplateModel; public class TwoPositionalParamsDirective extends TestTemplateDirectiveModel { @Override - public void execute(TemplateModel[] args, Writer out, Environment env, CallPlace callPlace) + public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) throws TemplateException, IOException { out.write("#p("); printParam("p1", args[0], out, true); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java new file mode 100644 index 0000000..38c438c --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/UpperCaseDirective.java @@ -0,0 +1,71 @@ +/* + * 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.userpkg; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; +import java.util.Collection; + +import org.apache.freemarker.core.Environment; +import org.apache.freemarker.core.TemplateException; +import org.apache.freemarker.core.model.CallPlace; +import org.apache.freemarker.core.model.TemplateModel; + +public class UpperCaseDirective extends TestTemplateDirectiveModel { + + @Override + public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) + throws TemplateException, IOException { + StringWriter capturingOut = new StringWriter(); + callPlace.executeNestedContent(null, capturingOut, env); + out.write(capturingOut.toString().toUpperCase()); + } + + @Override + public int getPredefinedPositionalArgumentCount() { + return 0; + } + + @Override + public boolean hasPositionalVarargsArgument() { + return false; + } + + @Override + public int getNamedArgumentIndex(String name) { + return -1; + } + + @Override + public int getNamedVarargsArgumentIndex() { + return -1; + } + + @Override + public int getTotalArgumentCount() { + return 0; + } + + @Override + public Collection<String> getPredefinedNamedArgumentNames() { + return null; + } +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java index dea9bcd..ecab36a 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirDynamicCall.java @@ -20,7 +20,9 @@ package org.apache.freemarker.core; import java.io.IOException; +import java.io.Writer; import java.util.Collection; +import java.util.LinkedHashMap; import org.apache.freemarker.core.model.CallPlace; import org.apache.freemarker.core.model.Constants; @@ -32,6 +34,7 @@ import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.util.BugException; import org.apache.freemarker.core.util.CommonSupplier; import org.apache.freemarker.core.util.StringToIndexMap; +import org.apache.freemarker.core.util._ArrayAdapterList; import org.apache.freemarker.core.util._StringUtil; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @@ -109,6 +112,32 @@ class ASTDirDynamicCall extends ASTDirective implements CallPlace { callableValue = (TemplateCallableModel) callableValueTM; directive = null; function = (TemplateFunctionModel) callableValue; + } else if (callableValueTM instanceof ASTDirMacro) { + // TODO [FM3][CF] Until macros were refactored to be TemplateDirectiveModel2-s, we have this hack here. + ASTDirMacro macro = (ASTDirMacro) callableValueTM; + if (macro.isFunction()) { + throw new _MiscTemplateException(env, + "Routine ", new _DelayedJQuote(macro.getName()), " is a function, not a directive. " + + "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ", + "<@someDirective someParam=f() />", "."); + } + + // We have to convert arguments to the legacy data structures... yet again, it's only a temporary hack. + LinkedHashMap<String, ASTExpression> macroNamedArgs; + if (namedArgs != null) { + macroNamedArgs = new LinkedHashMap<>(namedArgs.length * 4 / 3); + for (NamedArgument namedArg : namedArgs) { + macroNamedArgs.put(namedArg.name, namedArg.value); + } + } else { + macroNamedArgs = null; + } + env.invoke(macro, + macroNamedArgs, + _ArrayAdapterList.adapt(positionalArgs), + loopVarNames != null ? loopVarNames.getKeys() : null, + getChildBuffer()); + return null; } else if (callableValueTM == null) { throw InvalidReferenceException.getInstance(callableValueExp, env); } else { @@ -189,7 +218,7 @@ class ASTDirDynamicCall extends ASTDirective implements CallPlace { } if (directive != null) { - directive.execute(execArgs, env.getOut(), env, this); + directive.execute(execArgs, this, env.getOut(), env); } else { TemplateModel result = function.execute(execArgs, env, this); if (result == null) { @@ -349,9 +378,9 @@ class ASTDirDynamicCall extends ASTDirective implements CallPlace { } @Override - public void executeNestedContent(TemplateModel[] loopVariableValues, Environment env) + public void executeNestedContent(TemplateModel[] loopVariableValues, Writer out, Environment env) throws TemplateException, IOException { - env.visit(getChildBuffer(), loopVarNames, loopVariableValues); + env.visit(getChildBuffer(), loopVarNames, loopVariableValues, out); } @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java index a374215..c11ecef 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java @@ -434,7 +434,7 @@ final class ASTDirList extends ASTDirective { } @Override - public Collection getLocalVariableNames() { + public Collection<String> getLocalVariableNames() { String loopVarName = this.loopVarName; if (loopVarName != null) { if (localVarNames == null) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java index 6f6dede..4bed2fd 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacro.java @@ -151,13 +151,13 @@ final class ASTDirMacro extends ASTDirective implements TemplateModel { final Environment.Namespace localVars; final ASTElement[] nestedContentBuffer; final Environment.Namespace nestedContentNamespace; - final List nestedContentParameterNames; + final List<String> nestedContentParameterNames; final LocalContextStack prevLocalContextStack; final Context prevMacroContext; Context(Environment env, ASTElement[] nestedContentBuffer, - List nestedContentParameterNames) { + List<String> nestedContentParameterNames) { localVars = env.new Namespace(); this.nestedContentBuffer = nestedContentBuffer; nestedContentNamespace = env.getCurrentNamespace(); @@ -257,8 +257,8 @@ final class ASTDirMacro extends ASTDirective implements TemplateModel { } @Override - public Collection getLocalVariableNames() throws TemplateModelException { - HashSet result = new HashSet(); + public Collection<String> getLocalVariableNames() throws TemplateModelException { + HashSet<String> result = new HashSet<>(); for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) { result.add(it.next().toString()); } @@ -287,7 +287,7 @@ final class ASTDirMacro extends ASTDirective implements TemplateModel { } else if (idx == argDescsEnd) { return catchAllParamName; } else if (idx == argDescsEnd + 1) { - return Integer.valueOf(function ? TYPE_FUNCTION : TYPE_MACRO); + return function ? TYPE_FUNCTION : TYPE_MACRO; } else { throw new IndexOutOfBoundsException(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java index e915b01..da427ea 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java @@ -124,13 +124,13 @@ final class ASTDirNested extends ASTDirective { Context(Environment env) throws TemplateException { invokingMacroContext = env.getCurrentMacroContext(); - List bodyParameterNames = invokingMacroContext.nestedContentParameterNames; + List<String> bodyParameterNames = invokingMacroContext.nestedContentParameterNames; if (bodyParameters != null) { for (int i = 0; i < bodyParameters.size(); i++) { ASTExpression exp = (ASTExpression) bodyParameters.get(i); TemplateModel tm = exp.eval(env); if (bodyParameterNames != null && i < bodyParameterNames.size()) { - String bodyParameterName = (String) bodyParameterNames.get(i); + String bodyParameterName = bodyParameterNames.get(i); if (bodyVars == null) { bodyVars = env.new Namespace(); } @@ -146,9 +146,9 @@ final class ASTDirNested extends ASTDirective { } @Override - public Collection getLocalVariableNames() { - List bodyParameterNames = invokingMacroContext.nestedContentParameterNames; - return bodyParameterNames == null ? Collections.EMPTY_LIST : bodyParameterNames; + public Collection<String> getLocalVariableNames() { + List<String> bodyParameterNames = invokingMacroContext.nestedContentParameterNames; + return bodyParameterNames == null ? Collections.<String>emptyList() : bodyParameterNames; } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java index d19aaa2..931280c 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java @@ -40,6 +40,7 @@ import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; * AST directive node: {@code <@exp .../>} or {@code <@exp ...>...</@...>}. Calls an user-defined directive (like a * macro). */ +// TODO [FM3][CF] Remove final class ASTDirUserDefined extends ASTDirective implements DirectiveCallPlace { private ASTExpression nameExp; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/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 a47a83c..875366f 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 @@ -481,7 +481,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen } @Override - public Collection getLocalVariableNames() { + public Collection<String> getLocalVariableNames() { return bodyParameterNames; } }); @@ -497,28 +497,36 @@ public final class Environment extends MutableProcessingConfiguration<Environmen void visit( ASTElement[] childBuffer, - final StringToIndexMap loopVarNames, final TemplateModel[] loopVarValues) + final StringToIndexMap loopVarNames, final TemplateModel[] loopVarValues, + Writer out) throws IOException, TemplateException { - if (loopVarNames == null) { - visit(childBuffer); - } else { - pushLocalContext(new LocalContext() { - @Override - public TemplateModel getLocalVariable(String name) throws TemplateModelException { - int index = loopVarNames.get(name); - return index != -1 ? loopVarValues[index] : null; - } + // TODO [FM][CF] The plan is that `out` will be the root read only sink, so then this won't be here. + Writer prevOut = this.out; + this.out = out; + try { + if (loopVarNames == null) { + visit(childBuffer); + } else { + pushLocalContext(new LocalContext() { + @Override + public TemplateModel getLocalVariable(String name) throws TemplateModelException { + int index = loopVarNames.get(name); + return index != -1 ? loopVarValues[index] : null; + } - @Override - public Collection getLocalVariableNames() throws TemplateModelException { - return loopVarNames.getKeys(); + @Override + public Collection<String> getLocalVariableNames() throws TemplateModelException { + return loopVarNames.getKeys(); + } + }); + try { + visit(childBuffer); + } finally { + popLocalContext(); } - }); - try { - visit(childBuffer); - } finally { - popLocalContext(); } + } finally { + this.out = prevOut; } } @@ -760,8 +768,8 @@ public final class Environment extends MutableProcessingConfiguration<Environmen * Calls the macro or function with the given arguments and nested block. */ void invoke(ASTDirMacro macro, - Map namedArgs, List positionalArgs, - List bodyParameterNames, ASTElement[] childBuffer) throws TemplateException, IOException { + Map<String, ASTExpression> namedArgs, List<ASTExpression> positionalArgs, + List<String> bodyParameterNames, ASTElement[] childBuffer) throws TemplateException, IOException { if (macro == ASTDirMacro.DO_NOTHING_MACRO) { return; } @@ -803,7 +811,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen private void setMacroContextLocalsFromArguments( final ASTDirMacro.Context macroCtx, final ASTDirMacro macro, - final Map namedArgs, final List positionalArgs) throws TemplateException { + final Map<String, ASTExpression> namedArgs, final List<ASTExpression> positionalArgs) throws TemplateException { String catchAllParamName = macro.getCatchAll(); if (namedArgs != null) { final NativeHashEx2 catchAllParamValue; @@ -814,11 +822,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen catchAllParamValue = null; } - for (Map.Entry argNameAndValExp : (Set<Map.Entry>) namedArgs.entrySet()) { - final String argName = (String) argNameAndValExp.getKey(); + 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 = (ASTExpression) argNameAndValExp.getValue(); + ASTExpression argValueExp = argNameAndValExp.getValue(); TemplateModel argValue = argValueExp.eval(this); if (isArgNameDeclared) { macroCtx.setLocalVar(argName, argValue); @@ -849,7 +857,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen new _DelayedToString(argsCnt), "."); } for (int i = 0; i < argsCnt; i++) { - ASTExpression argValueExp = (ASTExpression) positionalArgs.get(i); + ASTExpression argValueExp = positionalArgs.get(i); TemplateModel argValue = argValueExp.eval(this); try { if (i < argNames.length) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/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 1084470..42c5455 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 @@ -31,6 +31,5 @@ import org.apache.freemarker.core.model.TemplateModelException; */ public interface LocalContext { TemplateModel getLocalVariable(String name) throws TemplateModelException; - Collection getLocalVariableNames() throws TemplateModelException; - + Collection<String> getLocalVariableNames() throws TemplateModelException; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java index f4f3f57..6518013 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java @@ -20,6 +20,7 @@ package org.apache.freemarker.core.model; import java.io.IOException; +import java.io.Writer; import java.util.IdentityHashMap; import org.apache.freemarker.core.CallPlaceCustomDataInitializationException; @@ -60,8 +61,8 @@ public interface CallPlace { * TemplateCallableModelUtils#EMPTY_TEMPLATE_MODEL_ARRAY}. Its length must be equal to * {@link #getLoopVariableCount()}. */ - void executeNestedContent(TemplateModel[] loopVariableValues, Environment env) throws TemplateException, - IOException; + void executeNestedContent(TemplateModel[] loopVariableValues, Writer out, Environment env) + throws TemplateException, IOException; // ------------------------------------------------------------------------------------------------------------- // Source code info: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java index e20d73a..00fd2d8 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java @@ -49,8 +49,9 @@ public interface TemplateCallableModel extends TemplateModel { boolean hasPositionalVarargsArgument(); /** - * Returns if with what array index should the given named argument by passed to the {@code execute} method. - * Consider using a static final {@link StringToIndexMap} field to implement this. + * For the given argument name (that corresponds to a parameter that meant to be passed by name, not by position) + * return its intended index in the {@code args} array argument of the {@code execute} method, or -1 if there's + * no such parameter. Consider using a static final {@link StringToIndexMap} field to implement this. * * @return -1 if there's no such named argument */ @@ -67,14 +68,16 @@ public interface TemplateCallableModel extends TemplateModel { int getNamedVarargsArgumentIndex(); /** - * The required length of the arguments array passed to the {@code execute} method. (It's possible that a longer - * array will be passed, in which case the extra elements should be ignored by {@code execute}.) - * The return value should be equal to the sum of these (but we don't want to calculate it on-the-fly, for speed), - * or else FreeMarker might fails later with {@link IndexOutOfBoundsException}: + * The required (minimum) length of the {@code args} array passed to the {@code execute} method. This length always + * includes the space reserved for optional arguments; it's not why it's said to be a minimum length. It's a minimum + * length because a longer array might be reused for better performance (but {@code execute} should never read + * those excess elements). + * The return value should be equal to the sum of these (but we don't want to calculate it on-the-fly, + * for speed), or else {@link IndexOutOfBoundsException}-s might will occur: * <ul> - * <li>{@link #getPredefinedPositionalArgumentCount()} + * <li>{@link #getPredefinedPositionalArgumentCount()} (note that predefined optional arguments are counted in) * <li>If {@link #hasPositionalVarargsArgument()} is {@code true}, then 1, else 0. - * <li>Size of {@link #getPredefinedNamedArgumentNames()} + * <li>Size of {@link #getPredefinedNamedArgumentNames()} (again, predefined optional arguments are counted in) * <li>If {@link #getNamedVarargsArgumentIndex()} is not -1, then 1, else 0. (Also, obviously, if * {@link #getNamedVarargsArgumentIndex()} is not -1, then it's one less than the return value of this method.) * </ul> @@ -82,7 +85,10 @@ public interface TemplateCallableModel extends TemplateModel { int getTotalArgumentCount(); /** - * The valid named argument names in the order as they should be displayed in error messages, or {@code null}. + * The valid names for arguments that are passed by name (not by position), in the order as they should be displayed + * in error messages, or {@code null} if there's none. If you have implemented + * {@link #getNamedArgumentIndex(String)} with a {@link StringToIndexMap}, you should return + * {@link StringToIndexMap#getKeys()} here. */ Collection<String> getPredefinedNamedArgumentNames(); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java index e430639..688e43f 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java @@ -9,20 +9,31 @@ import org.apache.freemarker.core.TemplateException; /** * A {@link TemplateCallableModel} that (progressively) prints it result into the {@code out} object, instead of * returning a single result at the end of the execution. Many of these won't print anything, but has other - * side-effects why it's useful for calling them. When used in an expression context, the printer output will be the - * value of the call (which depending on the output format of the directive is a {@link TemplateMarkupOutputModel}, - * or a {@link String}). + * side-effects why it's useful for calling them, or do flow control. They are used in templates like + * {@code <@myDirective foo=1 bar="wombat">...</@myDirective>} (or as {@code <@myDirective foo=1 bar="wombat" />} - + * the nested content is optional). + * <p> + * When called from expression context (and if the template language allows that!), the printed output will be captured, + * and will be the return value of the call. Depending on the output format of the directive, the type of that value + * will be {@link TemplateMarkupOutputModel} or {@link String}. */ // TODO [FM3][CF] Rename this to TemplateDirectiveModel public interface TemplateDirectiveModel2 extends TemplateCallableModel { /** - * @param args Array with {@link #getTotalArgumentCount()} elements (or more, in which case the extra elements - * should be ignored). If a parameter was omitted, the corresponding array element will be {@code null}. + * @param args + * Array with {@link #getTotalArgumentCount()} elements (or more, in which case the extra elements should be + * ignored). Not {@code null}. If a parameter was omitted, the corresponding array element will be {@code + * null}. + * @param callPlace + * The place (in a template, normally) where this directive was called from. Not {@code null}. Note that + * {@link CallPlace#executeNestedContent(TemplateModel[], Writer, Environment)} can be used to + * @param out + * Print the output here (if there's any) + * @param env + * The current processing environment. Not {@code null}. */ - void execute( - TemplateModel[] args, Writer out, - Environment env, CallPlace callPlace) + void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) throws TemplateException, IOException; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java index 9c7dd9c..55b7d5d 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java @@ -145,7 +145,7 @@ public final class StringToIndexMap { for (int i = 0; i < entriesLength; i++) { keyArray[i] = entries[i].key; } - keys = new _ArrayList<>(keyArray); + keys = _ArrayAdapterList.adapt(keyArray); // We try to find the best hash algorithm parameter (variation) for the known key set: int variation = 0; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java new file mode 100644 index 0000000..b653f7f --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayAdapterList.java @@ -0,0 +1,62 @@ +/* + * 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.util.AbstractList; +import java.util.Arrays; +import java.util.Iterator; + +/** + * Don't use this; used internally by FreeMarker, might changes without notice. + * Immutable list that wraps an array that's known to be non-changing. + */ +public class _ArrayAdapterList<E> extends AbstractList<E> { + + private final E[] array; + + public static <E> _ArrayAdapterList<E> adapt(E[] array) { + return array != null ? new _ArrayAdapterList<E>(array) : null; + } + + private _ArrayAdapterList(E[] array) { + this.array = array; + } + + @Override + public int size() { + return array.length; + } + + @Override + public E get(int index) { + return array[index]; + } + + @Override + public Iterator<E> iterator() { + return new _ArrayIterator(array); + } + + @Override + public Object[] toArray() { + return Arrays.copyOf(array, array.length); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/fbbfadb4/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayList.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayList.java deleted file mode 100644 index 5e1095e..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/_ArrayList.java +++ /dev/null @@ -1,58 +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.util; - -import java.util.AbstractList; -import java.util.Arrays; -import java.util.Iterator; - -/** - * Don't use this; used internally by FreeMarker, might changes without notice. - * Immutable list that wraps an array that's known to be non-changing. - */ -public class _ArrayList<E> extends AbstractList<E> { - - private final E[] array; - - public _ArrayList(E[] array) { - this.array = array; - } - - @Override - public int size() { - return array.length; - } - - @Override - public E get(int index) { - return array[index]; - } - - @Override - public Iterator<E> iterator() { - return new _ArrayIterator(array); - } - - @Override - public Object[] toArray() { - return Arrays.copyOf(array, array.length); - } - -}