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);
-    }
-
-}


Reply via email to