http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
new file mode 100644
index 0000000..2a34703
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java
@@ -0,0 +1,199 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model;
+
+import org.apache.freemarker.core.util.StringToIndexMap;
+
+/**
+ * {@link TemplateCallableModel} subinterfaces define a method called {@code 
execute}, which has an argument array
+ * parameter, whose layout this class describes. Each parameter has a constant 
index in this array, which is the same
+ * for all invocations of the same {@link TemplateCallableModel} object 
(regardless if there are omitted optional
+ * parameters). Thus, the argument values can always be accessed at these 
constant indexes; no runtime name lookup is
+ * needed inside the {@code execute} method of the {@link 
TemplateCallableModel} implementation. The
+ * {@link ArgumentArrayLayout} object is usually stored a static final field 
of the {@link TemplateCallableModel}
+ * implementation class.
+ * <p>
+ * The layout of the array is as follows:
+ * <ol>
+ * <li>
+ *     {@link #getPredefinedPositionalArgumentCount()} elements for the 
predefined positional parameters. Index 0
+ *     corresponds to the 1st positional parameter. For omitted parameters the 
corresponding array element is {@code
+ *     null}.
+ * <li>
+ *     {@link #getPredefinedNamedArgumentsMap()}{@code .size()} elements for 
the predefined named arguments. These are at
+ *     the indexes returned by {@link #getPredefinedNamedArgumentsMap()}{@code 
.get(String name)}. For omitted arguments
+ *     the corresponding array element is {@code null}.
+ * <li>
+ *     If there's a positional varargs argument, then one element for the 
positional varargs parameter, at
+ *     index {@link #getPositionalVarargsArgumentIndex()}.
+ * <li>
+ *     If there's a named varargs argument, then one element for the 
positional varargs parameter, at
+ *     index {@link #getNamedVarargsArgumentIndex()}.
+ * </ol>
+ * <p>
+ * The length of the array is {@link #getTotalLength()}}, or more, in which 
case the extra elements should be
+ * ignored.
+ * <p>
+ * Instances of this class are immutable, thread-safe objects.
+ */
+public final class ArgumentArrayLayout {
+    private final int predefinedPositionalArgumentCount;
+    private final StringToIndexMap predefinedNamedArgumentsMap;
+
+    private final int positionalVarargsArgumentIndex;
+    private final int namedVarargsArgumentIndex;
+    private final int arrayLength;
+
+    /** Constant to be used when the {@link TemplateCallableModel} has no 
parameters. */
+    public static final ArgumentArrayLayout PARAMETERLESS = new 
ArgumentArrayLayout(
+            0, false,
+            null, false);
+
+    /** Constant to be used when the {@link TemplateCallableModel} has 1 
positional parameter, and no others. */
+    public static final ArgumentArrayLayout SINGLE_POSITIONAL_PARAMETER = new 
ArgumentArrayLayout(
+            1, false,
+            null, false);
+
+    /**
+     * Creates a new instance, or returns some of the equivalent static 
constants (such as {@link #PARAMETERLESS} or
+     * {@link #SINGLE_POSITIONAL_PARAMETER}).
+     *
+     * @param predefinedPositionalArgumentCount
+     *         The highest allowed number of positional arguments, not 
counting the positional varargs argument. The
+     *         actual positional argument count can be less than this if there 
are optional positional argument. When
+     *         calling the {@code execute} method of the {@link 
TemplateCallableModel}, this many items will be reserved
+     *         for the positional arguments in the argument array (not 
counting the item for the positional varargs
+     *         argument, if there's one). Positional arguments above this 
count will go to the varargs argument (if
+     *         there's one, otherwise it's an error).
+     * @param hasPositionalVarargsArgument
+     *         Specifies if there's a varargs argument into which positional 
arguments that aren't predefined are
+     *         collected
+     * @param predefinedNamedArgumentsMap
+     *         The valid names for named arguments (not counting named varargs 
arguments), and their indexes in the
+     *         argument array passed to the {@code execute} method of the 
{@link TemplateCallableModel}. Can be {@code
+     *         null}, which is equivalent to {@link StringToIndexMap#EMPTY}. 
Indexes must fall into the range starting
+     *         with {@code predefinedPositionalArgumentCount} (inclusive), and 
ending with {@code
+     *         predefinedPositionalArgumentCount}{@code + 
predefinedNamedArgumentsMap.size()} (exclusive). If not, an
+     *         {@link IllegalArgumentException} will be thrown. (As {@link 
ArgumentArrayLayout}-s are normally created
+     *         during class initialization, such an exception will later cause 
{@link NoClassDefFoundError} "Could not
+     *         initialize" exceptions every time you try to access the class, 
which are not very informative. Only for
+     *         the first access of the class will you get an {@link 
ExceptionInInitializerError} with the {@link
+     *         IllegalArgumentException} as its cause exception, which 
contains the error details.)
+     * @param hasNamedVarargsArgument
+     *         Specifies if there's a varargs argument into which named 
arguments that aren't predefined are collected
+     *
+     * @throws IllegalArgumentException
+     *         If the {@code predefinedNamedArgumentsMap} contains indexes 
that are out of range. See the documentation
+     *         of that parameter for more.
+     */
+    public static ArgumentArrayLayout create(
+            int predefinedPositionalArgumentCount, boolean 
hasPositionalVarargsArgument,
+            StringToIndexMap predefinedNamedArgumentsMap, boolean 
hasNamedVarargsArgument) {
+        if ((predefinedNamedArgumentsMap == null || 
predefinedNamedArgumentsMap == StringToIndexMap.EMPTY)
+                && !hasPositionalVarargsArgument && !hasNamedVarargsArgument) {
+            if (predefinedPositionalArgumentCount == 0) {
+                return PARAMETERLESS;
+            }
+            if (predefinedPositionalArgumentCount == 1) {
+                return SINGLE_POSITIONAL_PARAMETER;
+            }
+        }
+        return new ArgumentArrayLayout(
+                predefinedPositionalArgumentCount, 
hasPositionalVarargsArgument,
+                predefinedNamedArgumentsMap, hasNamedVarargsArgument);
+    }
+
+    private ArgumentArrayLayout(int predefinedPositionalArgumentCount, boolean 
hasPositionalVarargsArgument,
+            StringToIndexMap predefinedNamedArgumentsMap, boolean 
hasNamedVarargsArgument) {
+        if (predefinedNamedArgumentsMap == null) {
+            predefinedNamedArgumentsMap = StringToIndexMap.EMPTY;
+        }
+
+        this.predefinedPositionalArgumentCount = 
predefinedPositionalArgumentCount;
+        this.predefinedNamedArgumentsMap = predefinedNamedArgumentsMap;
+
+        int arrayLength = predefinedPositionalArgumentCount + 
predefinedNamedArgumentsMap.size();
+        if (hasPositionalVarargsArgument) {
+            positionalVarargsArgumentIndex = arrayLength;
+            arrayLength++;
+        } else {
+            positionalVarargsArgumentIndex = -1;
+        }
+        if (hasNamedVarargsArgument) {
+            namedVarargsArgumentIndex = arrayLength;
+            arrayLength++;
+        } else {
+            namedVarargsArgumentIndex = -1;
+        }
+        this.arrayLength = arrayLength;
+
+        
predefinedNamedArgumentsMap.checkIndexRange(predefinedPositionalArgumentCount);
+    }
+
+    /**
+     * See the related parameter of {@link ArgumentArrayLayout#create(int, 
boolean, StringToIndexMap, boolean)}.
+     */
+    public int getPredefinedPositionalArgumentCount() {
+        return predefinedPositionalArgumentCount;
+    }
+
+    /**
+     * See the related parameter of {@link ArgumentArrayLayout#create(int, 
boolean, StringToIndexMap, boolean)}.
+     */
+    public StringToIndexMap getPredefinedNamedArgumentsMap() {
+        return predefinedNamedArgumentsMap;
+    }
+
+    /**
+     * Returns the index of the varargs argument into which positional 
arguments that aren't predefined are collected,
+     * or -1 if there's no such varargs argument. The value of the positional 
varargs argument is a {@link
+     * TemplateSequenceModel} that collects all positional arguments whose 
index would be greater than or equal to
+     * {@link #getPredefinedPositionalArgumentCount()}.
+     *
+     * @return -1 if there's no such named argument
+     */
+    public int getPositionalVarargsArgumentIndex() {
+        return positionalVarargsArgumentIndex;
+    }
+
+    /**
+     * Returns the index of the varargs argument into which named arguments 
that aren't predefined are collected, or -1
+     * if there's no such varargs argument. The value of the named varargs 
argument is a {@link TemplateHashModelEx2}
+     * with string keys that collects all the named arguments that aren't 
present in the {@link
+     * #getPredefinedNamedArgumentsMap()}. The iteration order of this hash 
follows the order in which the arguments
+     * were specified on the call site (in the template, typically).
+     *
+     * @return -1 if there's no such named argument
+     */
+    public int getNamedVarargsArgumentIndex() {
+        return namedVarargsArgumentIndex;
+    }
+
+    /**
+     * Returns the required (minimum) length of the {@code args} array that's 
passed to the {@code execute} method. As
+     * there's an index reserved for each predefined parameters, this length 
always includes the space reserved for
+     * optional parameters as well; 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).
+     */
+    public int getTotalLength() {
+        return arrayLength;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 6518013..7761eeb 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
@@ -24,10 +24,8 @@ import java.io.Writer;
 import java.util.IdentityHashMap;
 
 import org.apache.freemarker.core.CallPlaceCustomDataInitializationException;
-import org.apache.freemarker.core.DirectiveCallPlace;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.Template;
-import org.apache.freemarker.core.TemplateCallableModelUtils;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.util.CommonSupplier;
 
@@ -49,19 +47,29 @@ public interface CallPlace {
     boolean hasNestedContent();
 
     /**
-     * The number of loop variables specified for this call.
+     * The number of loop variables specified for this call. Note that users 
are generally allowed to omit loop
+     * variables when calling a directive. So this can be useful to avoid 
calculating the values of loop variables that
+     * the caller will not receive anyway. While it's an error if the user 
specifies too many loop variables, the
+     * implementor of the {@link TemplateDirectiveModel} shouldn't check for 
that condition, as far as they will call
+     * {@link #executeNestedContent(TemplateModel[], Writer, Environment)}, 
which will the should throw a {@link
+     * TemplateException} with a descriptive error message then.
      */
     int getLoopVariableCount();
 
     /**
      * Executed the nested content; it there's none, it just does nothing.
      *
-     * @param loopVariableValues
-     *         The loop variables to pass to the nested content; not {@code 
null} (use {@link
-     *         TemplateCallableModelUtils#EMPTY_TEMPLATE_MODEL_ARRAY}. Its 
length must be equal to
-     *         {@link #getLoopVariableCount()}.
+     * @param loopVarValues
+     *         The loop variable values to pass to the nested content, or 
{@code null} if there's none. It's not a
+     *         problem if this array is longer than the number of loop 
variables than the caller of the directive has
+     *         declared (as in {@code <@foo bar; i, j />} there are 2 loop 
variables declared); the directive
+     *         caller simply won't receive the excess variables. If the caller 
declares more loop variables than the
+     *         length of this array though, then a {@link TemplateException} 
will thrown by FreeMarker with a
+     *         descriptive error message. Thus, the caller of this method need 
not be concerned about the
+     *         number of loop variables declared by the caller (unless to 
avoid calculating loop variable values
+     *         unnecessarily, in which case use {@link 
#getLoopVariableCount()}).
      */
-    void executeNestedContent(TemplateModel[] loopVariableValues, Writer out, 
Environment env)
+    void executeNestedContent(TemplateModel[] loopVarValues, Writer out, 
Environment env)
             throws TemplateException, IOException;
 
     // 
-------------------------------------------------------------------------------------------------------------
@@ -102,41 +110,38 @@ public interface CallPlace {
     /**
      * 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. See the top-level documentation of {@link 
DirectiveCallPlace} to understand the scope and
-     * life-cycle of the custom data. Be sure that the custom data only 
depends on things that get their final value
-     * during template parsing, not on runtime settings.
-     *
+     * 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.
+     * 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.)
+     *         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}.
+     *         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.
+     *         If the {@link CommonSupplier} had to be invoked but failed.
      */
     Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> 
supplier)
             throws CallPlaceCustomDataInitializationException;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 3588ea7..2f30981 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
@@ -19,77 +19,11 @@
 
 package org.apache.freemarker.core.model;
 
-import java.util.Collection;
-
-import org.apache.freemarker.core.util.StringToIndexMap;
-
 /**
- * Super interface of {@link TemplateFunctionModel} and {@link 
TemplateDirectiveModel2}.
+ * Super interface of {@link TemplateFunctionModel} and {@link 
TemplateDirectiveModel}.
  */
 public interface TemplateCallableModel extends TemplateModel {
 
-    // 
-------------------------------------------------------------------------------------------------------------
-    // Arguments:
-
-    /**
-     * The highest allowed number of positional arguments, not counting the 
positional varargs argument. The actual
-     * positional argument count can be less than this if there are optional 
positional argument. When calling the
-     * {@code execute} method, this many items will be reserved for the 
positional arguments in the argument array (not
-     * counting the item for the positional varargs argument, if there's one). 
Positional arguments
-     * above this count will go to the varargs argument (if there's one, 
otherwise it's an error).
-     */
-    int getPredefinedPositionalArgumentCount();
-
-    /**
-     * Tells if there's no position varargs argument. If there is, then it 
must be in the argument array at the
-     * index equals to {@link #getPredefinedPositionalArgumentCount()}. The 
positional varargs argument is a
-     * {@link TemplateSequenceModel} that collects all positional arguments 
whose index would be greater than
-     * or equal to {@link #getPredefinedPositionalArgumentCount()}.
-     */
-    boolean hasPositionalVarargsArgument();
-
-    /**
-     * 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
-     */
-    int getPredefinedNamedArgumentIndex(String name);
-
-    /**
-     * Returns the index of the named varargs argument in the argument array, 
or -1 if there's no named varargs
-     * argument. The named varargs argument is a {@link TemplateHashModelEx2} 
with string keys that collects all
-     * the named arguments for which {@link 
#getPredefinedNamedArgumentIndex(String)} returns -1. The iteration order of 
this
-     * hash follows the order in which the arguments were specified in the 
calling template.
-     *
-     * @return -1 if there's no named varargs argument
-     */
-    int getNamedVarargsArgumentIndex();
-
-    /**
-     * 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 #getPredefinedNamedArgumentIndex(String)} with a {@link 
StringToIndexMap}, you should return
-     * {@link StringToIndexMap#getKeys()} here.
-     */
-    Collection<String> getPredefinedNamedArgumentNames();
-
-    /**
-     * 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()} (note that 
predefined optional arguments are counted in)
-     *     <li>If {@link #hasPositionalVarargsArgument()} is {@code true}, 
then 1, else 0.
-     *     <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>
-     */
-    int getArgumentArraySize();
+    ArgumentArrayLayout getArgumentArrayLayout();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java
deleted file mode 100644
index 787d9d8..0000000
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java
+++ /dev/null
@@ -1,43 +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.model;
-
-import java.io.IOException;
-import java.io.Writer;
-
-import org.apache.freemarker.core.TemplateException;
-
-/**
- * Represents the nested content of a directive ({@link 
TemplateDirectiveModel}) invocation. An implementation of this 
- * class is passed to {@link 
TemplateDirectiveModel#execute(org.apache.freemarker.core.Environment, 
- * java.util.Map, TemplateModel[], TemplateDirectiveBody)}. The implementation 
of the method is 
- * free to invoke it for any number of times, with any writer.
- */
-public interface TemplateDirectiveBody {
-    /**
-     * Renders the body of the directive body to the specified writer. The 
-     * writer is not flushed after the rendering. If you pass the environment's
-     * writer, there is no need to flush it. If you supply your own writer, you
-     * are responsible to flush/close it when you're done with using it (which
-     * might be after multiple renderings).
-     * @param out the writer to write the output to.
-     */
-    void render(Writer out) throws TemplateException, IOException;
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
index d9b2f96..6995afe 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java
@@ -1,67 +1,48 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
 package org.apache.freemarker.core.model;
 
 import java.io.IOException;
-import java.util.Map;
+import java.io.Writer;
 
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.util.DeepUnwrap;
 
 /**
- * "directive" template language data type: used as user-defined directives 
- * (much like macros) in templates. They can do arbitrary actions, write 
arbitrary
- * text to the template output, and trigger rendering of their nested content 
for
- * any number of times.
- * 
- * <p>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).
+ * 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, 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}.
+ * <p>
+ * Note that {@link TemplateDirectiveModel} is a relatively low-level 
interface that puts more emphasis on
+ * performance than on ease of implementation. TODO [FM3]: Introduce a more 
convenient way for implementing directives.
  */
-public interface TemplateDirectiveModel extends TemplateModel {
+public interface TemplateDirectiveModel extends TemplateCallableModel {
+
     /**
-     * Executes this user-defined directive; called by FreeMarker when the 
user-defined
-     * directive is called in the template.
+     * @param args
+     *         The of argument values. Not {@code null}. If a parameter was 
omitted on the caller side, the
+     *         corresponding array element will be {@code null}. For the 
indexed of arguments, see argument array layout
+     *         in the {@link TemplateCallableModel} documentation.
+     * @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 execute the
+     *         nested content. If the directive doesn't support nested 
content, it should check {@link
+     *         CallPlace#hasNestedContent()} that return {@code false}, and 
otherwise throw exception.
+     * @param out
+     *         Print the output here (if there's any)
+     * @param env
+     *         The current processing environment. Not {@code null}.
      *
-     * @param env the current processing environment. Note that you can access
-     * the output {@link java.io.Writer Writer} by {@link 
Environment#getOut()}.
-     * @param params the parameters (if any) passed to the directive as a 
-     * map of key/value pairs where the keys are {@link String}-s and the 
-     * values are {@link TemplateModel} instances. This is never 
-     * <code>null</code>. If you need to convert the template models to POJOs,
-     * you can use the utility methods in the {@link DeepUnwrap} class.
-     * @param loopVars an array that corresponds to the "loop variables", in
-     * the order as they appear in the directive call. ("Loop variables" are 
out-parameters
-     * that are available to the nested body of the directive; see in the 
Manual.)
-     * You set the loop variables by writing this array. The length of the 
array gives the
-     * number of loop-variables that the caller has specified.
-     * Never <code>null</code>, but can be a zero-length array.
-     * @param body an object that can be used to render the nested content 
(body) of
-     * the directive call. If the directive call has no nested content (i.e., 
it's like
-     * &lt;@myDirective /&gt; or &lt;@myDirective&gt;&lt;/@myDirective&gt;), 
then this will be
-     * <code>null</code>.
-     *
-     * @throws TemplateException If any problem occurs that's not an {@link 
IOException} during writing the template
-     *          output.
-     * @throws IOException When writing the template output fails.
+     * @throws TemplateException
+     *         If any problem occurs that's not an {@link IOException} during 
writing the template output.
+     * @throws IOException
+     *         When writing the template output fails.
      */
-    void execute(Environment env, Map params, TemplateModel[] loopVars,
-                 TemplateDirectiveBody body) throws TemplateException, 
IOException;
-}
\ No newline at end of file
+    void execute(TemplateModel[] args, CallPlace callPlace, Writer out, 
Environment env)
+            throws TemplateException, IOException;
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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
deleted file mode 100644
index 75bf2f2..0000000
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java
+++ /dev/null
@@ -1,51 +0,0 @@
-package org.apache.freemarker.core.model;
-
-import java.io.IOException;
-import java.io.Writer;
-
-import org.apache.freemarker.core.Environment;
-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, 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 #getArgumentArraySize()} elements (or more, 
in which case the extra elements should be
-     *         ignored). Not {@code null}. If a parameter was omitted on the 
caller side, the corresponding array
-     *         element will be {@code null}. Parameters passed by position 
will be at the index that corresponds to the
-     *         position (the 1st argument is at index 0). However, positional 
parameters over {@link
-     *         #getPredefinedPositionalArgumentCount()} will be in the 
positional varargs sequence at index one higher,
-     *         assuming {@link #hasPositionalVarargsArgument()} is {@code 
true}. Parameters passed by name (rather than
-     *         by position) will be at the index returned be {@link 
#getPredefinedNamedArgumentIndex(String)}, or in the
-     *         named varargs hash at index {@link 
#getNamedVarargsArgumentIndex()}, assuming that's not -1.
-     * @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 execute the
-     *         nested content. If the directive doesn't support nested 
content, it should check {@link
-     *         CallPlace#hasNestedContent()} that return {@code false}, and 
otherwise throw exception.
-     * @param out
-     *         Print the output here (if there's any)
-     * @param env
-     *         The current processing environment. Not {@code null}.
-     *
-     * @throws TemplateException
-     *         If any problem occurs that's not an {@link IOException} during 
writing the template output.
-     * @throws IOException
-     *         When writing the template output fails.
-     */
-    void execute(TemplateModel[] args, CallPlace callPlace, Writer out, 
Environment env)
-            throws TemplateException, IOException;
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java
index 7944d81..2ddfca0 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java
@@ -7,7 +7,7 @@ import org.apache.freemarker.core.outputformat.OutputFormat;
 
 /**
  * A {@link TemplateCallableModel}, which returns its result as a {@link 
TemplateModel} at the end of the execution.
- * This is in contrast with {@link TemplateDirectiveModel2}, which writes its 
result progressively to the output.
+ * This is in contrast with {@link TemplateDirectiveModel}, which writes its 
result progressively to the output.
  *
  * <p>Some template languages may allow function calls directly embedded into 
static text, as in
  * <code>text#f()text</code>. In that case, the language has to ensure that 
the return value is formatted according

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java
deleted file mode 100644
index 789d9bb..0000000
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java
+++ /dev/null
@@ -1,54 +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.model;
-
-import java.io.IOException;
-import java.io.Writer;
-import java.util.Map;
-
-import org.apache.freemarker.core.util.DeepUnwrap;
-
-/**
- * "transform" template language data type: user-defined directives 
- * (much like macros) specialized on filtering output; you should rather use 
the newer {@link TemplateDirectiveModel}
- * instead. This certainly will be deprecated in FreeMarker 2.4.
- */
-public interface TemplateTransformModel extends TemplateModel {
-
-     /**
-      * Returns a writer that will be used by the engine to feed the
-      * transformation input to the transform. Each call to this method
-      * must return a new instance of the writer so that the transformation
-      * is thread-safe.
-      * @param out the character stream to which to write the transformed 
output
-      * @param args the arguments (if any) passed to the transformation as a 
-      * map of key/value pairs where the keys are strings and the arguments are
-      * TemplateModel instances. This is never null. If you need to convert the
-      * template models to POJOs, you can use the utility methods in the 
-      * {@link DeepUnwrap} class.
-      * @return a writer to which the engine will feed the transformation 
-      * input, or null if the transform does not support nested content (body).
-      * The returned writer can implement the {@link TransformControl}
-      * interface if it needs advanced control over the evaluation of the 
-      * transformation body.
-      */
-     Writer getWriter(Writer out, Map args) 
-         throws TemplateModelException, IOException;
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java
deleted file mode 100644
index cd3965c..0000000
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java
+++ /dev/null
@@ -1,101 +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.model;
-
-import java.io.IOException;
-
-import org.apache.freemarker.core.TemplateException;
-
-/**
- * An interface that can be implemented by writers returned from
- * {@link TemplateTransformModel#getWriter(java.io.Writer, java.util.Map)}. The
- * methods on this
- * interfaces are callbacks that will be called by the template engine and that
- * give the writer a chance to better control the evaluation of the transform
- * body. The writer can instruct the engine to skip or to repeat body 
- * evaluation, and gets notified about exceptions that are thrown during the
- * body evaluation.
- */
-public interface TransformControl {
-    /**
-     * Constant returned from {@link #afterBody()} that tells the
-     * template engine to repeat transform body evaluation and feed
-     * it again to the transform.
-     */
-    int REPEAT_EVALUATION = 0;
-
-    /**
-     * Constant returned from {@link #afterBody()} that tells the
-     * template engine to end the transform and close the writer.
-     */
-    int END_EVALUATION = 1;
- 
-    /**
-     * Constant returned from {@link #onStart()} that tells the
-     * template engine to skip evaluation of the body.
-     */
-    int SKIP_BODY = 0;
-    
-    /**
-     * Constant returned from {@link #onStart()} that tells the
-     * template engine to evaluate the body.
-     */
-    int EVALUATE_BODY = 1;
-
-    /**
-     * Called before the body is evaluated for the first time.
-     * @return 
-     * <ul>
-     * <li><tt>SKIP_BODY</tt> if the transform wants to ignore the body. In 
this
-     * case, only {@link java.io.Writer#close()} is called next and processing 
ends.</li>
-     * <li><tt>EVALUATE_BODY</tt> to normally evaluate the body of the 
transform
-     * and feed it to the writer</li>
-     * </ul>
-     */
-    int onStart() throws TemplateModelException, IOException;
-    
-    /**
-     * Called after the body has been evaluated.
-     * @return
-     * <ul>
-     * <li><tt>END_EVALUATION</tt> if the transformation should be ended.</li>
-     * <li><tt>REPEAT_EVALUATION</tt> to have the engine re-evaluate the 
-     * transform body and feed it again to the writer.</li>
-     * </ul>
-     */
-    int afterBody() throws TemplateModelException, IOException;
-    
-    /**
-     * Called if any exception occurs during the transform between the
-     * {@link TemplateTransformModel#getWriter(java.io.Writer, java.util.Map)} 
call
-     * and the {@link java.io.Writer#close()} call.
-     * @param t the throwable that represents the exception. It can be any 
-     * non-checked throwable, as well as {@link TemplateException} and 
-     * {@link java.io.IOException}.
-     * 
-     * @throws Throwable is recommended that the methods rethrow the received 
-     * throwable. If the method wants to throw another throwable, it should
-     * either throw a non-checked throwable, or an instance of 
-     * {@link TemplateException} and {@link java.io.IOException}. Throwing any
-     * other checked exception will cause the engine to rethrow it as
-     * a {@link java.lang.reflect.UndeclaredThrowableException}.
-     */
-    void onError(Throwable t) throws Throwable;
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
index 1da4f62..647ad90 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java
@@ -40,7 +40,6 @@ import org.apache.freemarker.core.model.TemplateNodeModelEx;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.TemplateTransformModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
 import org.apache.freemarker.core.model.impl.BeanAndStringModel;
 import org.apache.freemarker.core.model.impl.BeanModel;
@@ -774,8 +773,6 @@ public final class FTLUtil {
 
         if (TemplateDirectiveModel.class.isAssignableFrom(cl)) {
             appendTypeName(sb, typeNamesAppended, "directive");
-        } else if (TemplateTransformModel.class.isAssignableFrom(cl)) {
-            appendTypeName(sb, typeNamesAppended, "transform");
         }
 
         if (TemplateSequenceModel.class.isAssignableFrom(cl)) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/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 55b7d5d..9edb980 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
@@ -28,6 +28,8 @@ import java.util.Map;
  * Maps string keys to non-negative int-s. This isn't a {@link Map}, but a 
more specialized class. It's immutable.
  * It's slower to create than {@link HashMap}, but usually is a bit faster to 
read, and when {@link HashMap} gets
  * unlucky with clashing hash keys, then it can be significantly faster.
+ * <p>
+ * Instances of this class are immutable, thread-safe objects.
  */
 public final class StringToIndexMap {
 
@@ -239,6 +241,31 @@ public final class StringToIndexMap {
         return keys != null ? keys.size() : 1;
     }
 
+    /**
+     * Checks if all entries are in the {@code start} - {@code start}+{@code 
size()} (exclusive) index range.
+     *
+     * @throws IllegalArgumentException If some entry is not in the specified 
index range.
+     */
+    public void checkIndexRange(int start) {
+        if (buckets == null) {
+            return;
+        }
+
+        int end = start + size();
+        for (Entry bucket : buckets) {
+            Entry entry = bucket;
+            while (entry != null) {
+                if (entry.value < start || entry.value >= end) {
+                    throw new IllegalArgumentException(
+                            "Entry " + _StringUtil.jQuote(entry.key) + " -> " 
+ entry.value
+                            + " is out of allowed index range: " + start + " 
..< " + end);
+                }
+                entry = entry.nextInSameBucket;
+            }
+        }
+
+    }
+
     private static int getPowerOf2GreaterThanOrEqualTo(int n) {
         if (n == 0) {
             return 0;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/javacc/FTL.jj 
b/freemarker-core/src/main/javacc/FTL.jj
index c42c7a9..9de0521 100644
--- a/freemarker-core/src/main/javacc/FTL.jj
+++ b/freemarker-core/src/main/javacc/FTL.jj
@@ -35,7 +35,7 @@ import org.apache.freemarker.core.outputformat.impl.*;
 import org.apache.freemarker.core.model.*;
 import org.apache.freemarker.core.model.impl.*;
 import org.apache.freemarker.core.util.*;
-import org.apache.freemarker.core.ASTDirDynamicCall.NamedArgument;
+import org.apache.freemarker.core.ASTDynamicTopLevelCall.NamedArgument;
 import java.io.*;
 import java.util.*;
 import java.nio.charset.Charset;
@@ -569,7 +569,7 @@ TOKEN_MGR_DECLS:
         return image.charAt(idx + charIdxInName);
     }
 
-    private void unifiedCall(Token tok) {
+    private void dynamicTopLevelCall(Token tok) {
         char firstChar = tok.image.charAt(0);
         if (autodetectTagSyntax && !directiveSyntaxEstablished) {
             squBracTagSyntax = (firstChar == '[');
@@ -586,7 +586,7 @@ TOKEN_MGR_DECLS:
         SwitchTo(NO_SPACE_EXPRESSION);
     }
 
-    private void unifiedCallEnd(Token tok) {
+    private void dynamicTopLevelCallEnd(Token tok) {
         char firstChar = tok.image.charAt(0);
         if (squBracTagSyntax && firstChar == '<') {
             tok.kind = STATIC_TEXT_NON_WS;
@@ -846,13 +846,9 @@ TOKEN:
         handleTagSyntaxAndSwitch(matchedToken, DEFAULT);
     }
     |
-    <UNIFIED_CALL : "<@" | "[@" > { unifiedCall(matchedToken); }
+    <DYNAMIC_TOP_LEVEL_CALL : "<@" | "[@" > { 
dynamicTopLevelCall(matchedToken); }
     |
-    <UNIFIED_CALL_END : ("<" | "[") "/@" ((<ID>) ("."<ID>)*)? <CLOSE_TAG1>> { 
unifiedCallEnd(matchedToken); }
-    |
-    <DYNAMIC_DIRECTIVE_CALL : "<~" | "[~" > { unifiedCall(matchedToken); }
-    |
-    <DYNAMIC_DIRECTIVE_CALL_END : ("<" | "[") "/~" ((<ID>) ("."<ID>)*)? 
<CLOSE_TAG1>> { unifiedCallEnd(matchedToken); }
+    <DYNAMIC_TOP_LEVEL_CALL_END : ("<" | "[") "/@" ((<ID>) ("."<ID>)*)? 
<CLOSE_TAG1>> { dynamicTopLevelCallEnd(matchedToken); }
     |
     <FTL_HEADER : ("<#ftl" | "[#ftl") <BLANK>> { ftlHeader(matchedToken); }
     |
@@ -3034,102 +3030,7 @@ ASTDirCompress Compress() :
     }
 }
 
-ASTElement UnifiedMacroTransform() :
-{
-    Token start = null, end, t;
-    HashMap namedArgs = null;
-    ArrayList positionalArgs = null, bodyParameters = null;
-    ASTExpression startTagNameExp;
-    TemplateElements children;
-    ASTExpression exp;
-    int pushedCtxCount = 0;
-}
-{
-    start = <UNIFIED_CALL>
-    exp = ASTExpression()
-    {
-        if (exp instanceof ASTExpVariable || (exp instanceof ASTExpDot && 
((ASTExpDot) exp).onlyHasIdentifiers())) {
-            startTagNameExp = exp;
-        } else {
-            startTagNameExp = null;
-        }
-    }
-    [<TERMINATING_WHITESPACE>]
-    (
-        LOOKAHEAD(<ID><ASSIGNMENT_EQUALS>)
-        namedArgs = NamedArgs()
-        |
-        positionalArgs = PositionalArgs()
-    )
-    [
-        <SEMICOLON>{bodyParameters = new ArrayList(4); }
-        [
-            t = <ID> { bodyParameters.add(t.image); }
-            (
-                <COMMA>
-                t = <ID>{bodyParameters.add(t.image); }
-            )*
-        ]
-    ]
-    (
-        end = <EMPTY_DIRECTIVE_END> { children = TemplateElements.EMPTY; }
-        |
-        (
-            <DIRECTIVE_END> {
-                if (bodyParameters != null && iteratorBlockContexts != null && 
!iteratorBlockContexts.isEmpty()) {
-                    // It's possible that we shadow a #list/#items loop 
variable, in which case that must be noted.
-                    int ctxsLen = iteratorBlockContexts.size();
-                    int bodyParsLen = bodyParameters.size();
-                       for (int bodyParIdx = 0; bodyParIdx < bodyParsLen; 
bodyParIdx++) {
-                        String bodyParName = (String) 
bodyParameters.get(bodyParIdx);
-                        walkCtxSack: for (int ctxIdx = ctxsLen - 1; ctxIdx >= 
0; ctxIdx--) {
-                            ParserIteratorBlockContext ctx
-                                    = (ParserIteratorBlockContext) 
iteratorBlockContexts.get(ctxIdx);
-                            if (ctx.loopVarName != null && 
ctx.loopVarName.equals(bodyParName)) {
-                                // If it wasn't already shadowed, shadow it:
-                                if (ctx.kind != 
ITERATOR_BLOCK_KIND_USER_DIRECTIVE) {
-                                    ParserIteratorBlockContext shadowingCtx = 
pushIteratorBlockContext();
-                                    shadowingCtx.loopVarName = bodyParName;
-                                    shadowingCtx.kind = 
ITERATOR_BLOCK_KIND_USER_DIRECTIVE;
-                                    pushedCtxCount++;
-                                }
-                                break walkCtxSack;
-                            }
-                        }
-                   }
-                }
-            }
-            children = MixedContentElements()
-            end = <UNIFIED_CALL_END>
-            {
-                for (int i = 0; i < pushedCtxCount; i++) {
-                    popIteratorBlockContext();
-                }
-            
-                String endTagName = end.image.substring(3, end.image.length() 
- 1).trim();
-                if (endTagName.length() > 0) {
-                    if (startTagNameExp == null) {
-                        throw new ParseException("Expecting </@>", template, 
end);
-                    } else {
-                        String startTagName = 
startTagNameExp.getCanonicalForm();
-                        if (!endTagName.equals(startTagName)) {
-                            throw new ParseException("Expecting </@> or </@" + 
startTagName + ">", template, end);
-                        }
-                    }
-                }
-            }
-        )
-    )
-    {
-        ASTElement result = (positionalArgs != null)
-                       ? new ASTDirUserDefined(exp, positionalArgs, children, 
bodyParameters)
-                   : new ASTDirUserDefined(exp, namedArgs, children, 
bodyParameters);
-        result.setLocation(template, start, end);
-        return result;
-    }
-}
-
-ASTElement DynamicDirectiveCall() :
+ASTElement DynamicTopLevelCall() :
 {
     Token t;
     ASTExpression exp;
@@ -3142,7 +3043,7 @@ ASTElement DynamicDirectiveCall() :
     int pushedCtxCount = 0;
 }
 {
-    start = <DYNAMIC_DIRECTIVE_CALL>
+    start = <DYNAMIC_TOP_LEVEL_CALL>
     callableValueExp = ASTExpression()
     [<TERMINATING_WHITESPACE>]
     {
@@ -3278,7 +3179,7 @@ ASTElement DynamicDirectiveCall() :
 
             children = MixedContentElements()
 
-            end = <DYNAMIC_DIRECTIVE_CALL_END>
+            end = <DYNAMIC_TOP_LEVEL_CALL_END>
             {
                 for (int i = 0; i < pushedCtxCount; i++) {
                     popIteratorBlockContext();
@@ -3301,7 +3202,7 @@ ASTElement DynamicDirectiveCall() :
         )
     )
     {
-        ASTElement result = new ASTDirDynamicCall(
+        ASTElement result = new ASTDynamicTopLevelCall(
                 callableValueExp, false,
                 trimmedPositionalArgs, trimmedNamedArgs, loopVarNames,
                 children);
@@ -3715,9 +3616,7 @@ ASTElement FreemarkerDirective() :
         |
         tp = Compress()
         |
-        tp = UnifiedMacroTransform()
-        |
-        tp = DynamicDirectiveCall()
+        tp = DynamicTopLevelCall()
         |
         tp = Items()
         |

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
index 608f1bb..c6adead 100644
--- 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java
@@ -40,12 +40,14 @@ import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core._DelayedFTLTypeDescription;
 import org.apache.freemarker.core._MiscTemplateException;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.util.DeepUnwrap;
+import org.apache.freemarker.core.util.StringToIndexMap;
 
 
 /**
@@ -60,26 +62,45 @@ import org.apache.freemarker.core.util.DeepUnwrap;
  * values of parameters.
  */
 public class IncludePage implements TemplateDirectiveModel {
+
     private final HttpServletRequest request;
     private final HttpServletResponse response;
+
+    private static final int PATH_PARAM_IDX = 0;
+    private static final int INHERIT_PARAMS_PARAM_IDX = 1;
+    private static final int PARAMS_PARAM_IDX = 2;
+
+    private static final String PATH_PARAM_NAME = "path";
+    private static final String INHERIT_PARAMS_PARAM_NAME = "inherit_params";
+    private static final String PARAMS_PARAM_NAME = "params";
+
+    private static final StringToIndexMap NAME_TO_IDX_MAP = 
StringToIndexMap.of(
+            PATH_PARAM_NAME, PATH_PARAM_IDX,
+            INHERIT_PARAMS_PARAM_NAME, INHERIT_PARAMS_PARAM_IDX
+    );
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = 
ArgumentArrayLayout.create(
+            0, false,
+            NAME_TO_IDX_MAP, false
+    );
     
     public IncludePage(HttpServletRequest request, HttpServletResponse 
response) {
         this.request = request;
         this.response = response;
     }
-    
+
     @Override
-    public void execute(final Environment env, Map params,
-                        TemplateModel[] loopVars, TemplateDirectiveBody body)
-    throws TemplateException, IOException {
+    public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, 
Environment env)
+            throws TemplateException, IOException {
         // Determine the path
-        final TemplateModel path = (TemplateModel) params.get("path");
+        final TemplateModel path = args[PATH_PARAM_IDX];
         if (path == null) {
             throw new _MiscTemplateException(env, "Missing required parameter 
\"path\"");
         }
         if (!(path instanceof TemplateScalarModel)) {
             throw new _MiscTemplateException(env,
-                    "Expected a scalar model. \"path\" is instead ", new 
_DelayedFTLTypeDescription(path));
+                    "Expected a scalar model. \"", PATH_PARAM_NAME, "\" is 
instead ",
+                    new _DelayedFTLTypeDescription(path));
         }
         final String strPath = ((TemplateScalarModel) path).getAsString();
         if (strPath == null) {
@@ -113,21 +134,21 @@ public class IncludePage implements 
TemplateDirectiveModel {
 
         // Determine inherit_params value
         final boolean inheritParams;
-        final TemplateModel inheritParamsModel = (TemplateModel) 
params.get("inherit_params");
+        final TemplateModel inheritParamsModel = 
args[INHERIT_PARAMS_PARAM_IDX];
         if (inheritParamsModel == null) {
             // defaults to true when not specified
             inheritParams = true; 
         } else {
             if (!(inheritParamsModel instanceof TemplateBooleanModel)) {
                 throw new _MiscTemplateException(env,
-                        "\"inherit_params\" should be a boolean but it's a(n) 
",
+                        "\"", INHERIT_PARAMS_PARAM_NAME, "\" should be a 
boolean but it's a(n) ",
                         inheritParamsModel.getClass().getName(), " instead");
             }
             inheritParams = ((TemplateBooleanModel) 
inheritParamsModel).getAsBoolean();
         }
         
         // Get explicit params, if any
-        final TemplateModel paramsModel = (TemplateModel) params.get("params");
+        final TemplateModel paramsModel = args[PARAMS_PARAM_IDX];
         
         // Determine whether we need to wrap the request
         final HttpServletRequest wrappedRequest;
@@ -143,7 +164,7 @@ public class IncludePage implements TemplateDirectiveModel {
                 final Object unwrapped = DeepUnwrap.unwrap(paramsModel);
                 if (!(unwrapped instanceof Map)) {
                     throw new _MiscTemplateException(env,
-                            "Expected \"params\" to unwrap into a 
java.util.Map. It unwrapped into ",
+                            "Expected \"", PARAMS_PARAM_NAME, "\" to unwrap 
into a java.util.Map. It unwrapped into ",
                             unwrapped.getClass().getName(), " instead.");
                 }
                 paramsMap = (Map) unwrapped;
@@ -163,6 +184,11 @@ public class IncludePage implements TemplateDirectiveModel 
{
         }
     }
 
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
     private static final class CustomParamsRequest extends 
HttpServletRequestWrapper {
         private final HashMap paramsMap;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java
new file mode 100644
index 0000000..af0a8e0
--- /dev/null
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java
@@ -0,0 +1,222 @@
+/*
+ * 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.servlet.jsp;
+
+import java.io.CharArrayReader;
+import java.io.CharArrayWriter;
+import java.io.IOException;
+import java.io.Reader;
+import java.io.Writer;
+
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.BodyContent;
+
+/**
+ * An implementation of BodyContent that buffers it's input to a char[].
+ */
+class BodyContentImpl extends BodyContent {
+    private CharArrayWriter buf;
+
+    BodyContentImpl(JspWriter out, boolean buffer) {
+        super(out);
+        if (buffer) initBuffer();
+    }
+
+    void initBuffer() {
+        buf = new CharArrayWriter();
+    }
+
+    @Override
+    public void flush() throws IOException {
+        if (buf == null) {
+            getEnclosingWriter().flush();
+        }
+    }
+
+    @Override
+    public void clear() throws IOException {
+        if (buf != null) {
+            buf = new CharArrayWriter();
+        } else {
+            throw new IOException("Can't clear");
+        }
+    }
+
+    @Override
+    public void clearBuffer() throws IOException {
+        if (buf != null) {
+            buf = new CharArrayWriter();
+        } else {
+            throw new IOException("Can't clear");
+        }
+    }
+
+    @Override
+    public int getRemaining() {
+        return Integer.MAX_VALUE;
+    }
+
+    @Override
+    public void newLine() throws IOException {
+        write(JspWriterAdapter.NEWLINE);
+    }
+
+    @Override
+    public void close() throws IOException {
+    }
+
+    @Override
+    public void print(boolean arg0) throws IOException {
+        write(arg0 ? Boolean.TRUE.toString() : Boolean.FALSE.toString());
+    }
+
+    @Override
+    public void print(char arg0) throws IOException {
+        write(arg0);
+    }
+
+    @Override
+    public void print(char[] arg0) throws IOException {
+        write(arg0);
+    }
+
+    @Override
+    public void print(double arg0) throws IOException {
+        write(Double.toString(arg0));
+    }
+
+    @Override
+    public void print(float arg0) throws IOException {
+        write(Float.toString(arg0));
+    }
+
+    @Override
+    public void print(int arg0) throws IOException {
+        write(Integer.toString(arg0));
+    }
+
+    @Override
+    public void print(long arg0) throws IOException {
+        write(Long.toString(arg0));
+    }
+
+    @Override
+    public void print(Object arg0) throws IOException {
+        write(arg0 == null ? "null" : arg0.toString());
+    }
+
+    @Override
+    public void print(String arg0) throws IOException {
+        write(arg0);
+    }
+
+    @Override
+    public void println() throws IOException {
+        newLine();
+    }
+
+    @Override
+    public void println(boolean arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(char arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(char[] arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(double arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(float arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(int arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(long arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(Object arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void println(String arg0) throws IOException {
+        print(arg0);
+        newLine();
+    }
+
+    @Override
+    public void write(int c) throws IOException {
+        if (buf != null) {
+            buf.write(c);
+        } else {
+            getEnclosingWriter().write(c);
+        }
+    }
+
+    @Override
+    public void write(char[] cbuf, int off, int len) throws IOException {
+        if (buf != null) {
+            buf.write(cbuf, off, len);
+        } else {
+            getEnclosingWriter().write(cbuf, off, len);
+        }
+    }
+
+    @Override
+    public String getString() {
+        return buf.toString();
+    }
+
+    @Override
+    public Reader getReader() {
+        return new CharArrayReader(buf.toCharArray());
+    }
+
+    @Override
+    public void writeOut(Writer out) throws IOException {
+        buf.writeTo(out);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
index cfb91b6..c51e611 100644
--- 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java
@@ -21,17 +21,16 @@ package org.apache.freemarker.servlet.jsp;
 import java.io.IOException;
 import java.io.Writer;
 import java.util.List;
-import java.util.Map;
 
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateMethodModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateTransformModel;
 import org.apache.freemarker.core.model.impl.JavaMethodModel;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._ClassUtil;
@@ -46,7 +45,7 @@ class CustomTagAndELFunctionCombiner {
 
     /**
      * @param customTag
-     *            Either a {@link TemplateDirectiveModel} or a {@link 
TemplateTransformModel}.
+     *            A {@link TemplateDirectiveModel}.
      */
     static TemplateModel combine(TemplateModel customTag, 
TemplateMethodModelEx elFunction) {
         if (customTag instanceof TemplateDirectiveModel) {
@@ -55,12 +54,6 @@ class CustomTagAndELFunctionCombiner {
                             (TemplateDirectiveModel) customTag, 
(JavaMethodModel) elFunction) //
                     : new TemplateDirectiveModelAndTemplateMethodModelEx( //
                             (TemplateDirectiveModel) customTag, elFunction);
-        } else if (customTag instanceof TemplateTransformModel) {
-            return (elFunction instanceof JavaMethodModel)
-                    ? new TemplateTransformModelAndSimpleMethodModel( //
-                            (TemplateTransformModel) customTag, 
(JavaMethodModel) elFunction) //
-                    : new TemplateTransformModelAndTemplateMethodModelEx( //
-                            (TemplateTransformModel) customTag, elFunction);
         } else {
             throw new BugException(
                     "Unexpected custom JSP tag class: " + 
_ClassUtil.getShortClassNameOfObject(customTag));
@@ -72,8 +65,7 @@ class CustomTagAndELFunctionCombiner {
      * {@link #combine(TemplateModel, TemplateMethodModelEx)}.
      */
     static boolean canBeCombinedAsCustomTag(TemplateModel tm) {
-        return (tm instanceof TemplateDirectiveModel || tm instanceof 
TemplateTransformModel)
-                && !(tm instanceof CombinedTemplateModel);
+        return (tm instanceof TemplateDirectiveModel) && !(tm instanceof 
CombinedTemplateModel);
     }
 
     /**
@@ -107,16 +99,20 @@ class CustomTagAndELFunctionCombiner {
         }
 
         @Override
-        public void execute(Environment env, Map params, TemplateModel[] 
loopVars, TemplateDirectiveBody body)
-                throws TemplateException, IOException {
-            templateDirectiveModel.execute(env, params, loopVars, body);
+        public Object[] explainTypeError(Class[] expectedClasses) {
+            return simpleMethodModel.explainTypeError(expectedClasses);
         }
 
         @Override
-        public Object[] explainTypeError(Class[] expectedClasses) {
-            return simpleMethodModel.explainTypeError(expectedClasses);
+        public void execute(TemplateModel[] args, CallPlace callPlace, Writer 
out, Environment env)
+                throws TemplateException, IOException {
+            templateDirectiveModel.execute(args, callPlace, out, env);
         }
 
+        @Override
+        public ArgumentArrayLayout getArgumentArrayLayout() {
+            return templateDirectiveModel.getArgumentArrayLayout();
+        }
     }
 
     private static class TemplateDirectiveModelAndTemplateMethodModelEx 
extends CombinedTemplateModel
@@ -137,64 +133,15 @@ class CustomTagAndELFunctionCombiner {
         }
 
         @Override
-        public void execute(Environment env, Map params, TemplateModel[] 
loopVars, TemplateDirectiveBody body)
+        public void execute(TemplateModel[] args, CallPlace callPlace, Writer 
out, Environment env)
                 throws TemplateException, IOException {
-            templateDirectiveModel.execute(env, params, loopVars, body);
-        }
-
-    }
-
-    private static class TemplateTransformModelAndTemplateMethodModelEx 
extends CombinedTemplateModel
-            implements TemplateTransformModel, TemplateMethodModelEx {
-
-        private final TemplateTransformModel templateTransformModel;
-        private final TemplateMethodModelEx templateMethodModelEx;
-
-        public TemplateTransformModelAndTemplateMethodModelEx( //
-                TemplateTransformModel templateTransformModel, 
TemplateMethodModelEx templateMethodModelEx) {
-            this.templateTransformModel = templateTransformModel;
-            this.templateMethodModelEx = templateMethodModelEx;
+            templateDirectiveModel.execute(args, callPlace, out, env);
         }
 
         @Override
-        public Object exec(List arguments) throws TemplateModelException {
-            return templateMethodModelEx.exec(arguments);
+        public ArgumentArrayLayout getArgumentArrayLayout() {
+            return templateDirectiveModel.getArgumentArrayLayout();
         }
-
-        @Override
-        public Writer getWriter(Writer out, Map args) throws 
TemplateModelException, IOException {
-            return templateTransformModel.getWriter(out, args);
-        }
-
-    }
-
-    private static class TemplateTransformModelAndSimpleMethodModel extends 
CombinedTemplateModel
-            implements TemplateTransformModel, TemplateMethodModelEx, 
_UnexpectedTypeErrorExplainerTemplateModel {
-
-        private final TemplateTransformModel templateTransformModel;
-        private final JavaMethodModel simpleMethodModel;
-
-        public TemplateTransformModelAndSimpleMethodModel( //
-                TemplateTransformModel templateTransformModel, JavaMethodModel 
simpleMethodModel) {
-            this.templateTransformModel = templateTransformModel;
-            this.simpleMethodModel = simpleMethodModel;
-        }
-
-        @Override
-        public Object exec(List arguments) throws TemplateModelException {
-            return simpleMethodModel.exec(arguments);
-        }
-
-        @Override
-        public Object[] explainTypeError(Class[] expectedClasses) {
-            return simpleMethodModel.explainTypeError(expectedClasses);
-        }
-
-        @Override
-        public Writer getWriter(Writer out, Map args) throws 
TemplateModelException, IOException {
-            return templateTransformModel.getWriter(out, args);
-        }
-
     }
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java
index 4564d71..80f3f2b 100644
--- 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java
@@ -387,7 +387,7 @@ abstract class FreeMarkerPageContext extends PageContext 
implements TemplateMode
 
     @Override
     public BodyContent pushBody() {
-      return (BodyContent) pushWriter(new 
TagTransformModel.BodyContentImpl(getOut(), true));
+      return (BodyContent) pushWriter(new BodyContentImpl(getOut(), true));
   }
 
   @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
index 8c6fd3a..99e8272 100644
--- 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
@@ -27,7 +27,6 @@ import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.math.BigDecimal;
 import java.util.HashMap;
-import java.util.Iterator;
 import java.util.Map;
 
 import org.apache.freemarker.core.Template;
@@ -36,8 +35,9 @@ import org.apache.freemarker.core._DelayedShortClassName;
 import org.apache.freemarker.core._ErrorDescriptionBuilder;
 import org.apache.freemarker.core._TemplateModelException;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
-import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateScalarModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.util._StringUtil;
 import 
org.apache.freemarker.servlet.jsp.SimpleTagDirectiveModel.TemplateExceptionWrapperJspException;
@@ -75,17 +75,17 @@ class JspTagModelBase {
         return tagClass.newInstance();
     }
     
-    void setupTag(Object tag, Map args, ObjectWrapperAndUnwrapper wrapper)
-    throws TemplateModelException, 
+    void setupTag(Object tag, TemplateHashModelEx2 args, 
ObjectWrapperAndUnwrapper wrapper)
+            throws TemplateModelException,
         InvocationTargetException, 
         IllegalAccessException {
         if (args != null && !args.isEmpty()) {
             final Object[] argArray = new Object[1];
-            for (Iterator iter = args.entrySet().iterator(); iter.hasNext(); ) 
{
-                final Map.Entry entry = (Map.Entry) iter.next();
-                final Object arg = wrapper.unwrap((TemplateModel) 
entry.getValue());
+            for (TemplateHashModelEx2.KeyValuePairIterator iter = 
args.keyValuePairIterator(); iter.hasNext(); ) {
+                final TemplateHashModelEx2.KeyValuePair entry = iter.next();
+                final Object arg = wrapper.unwrap(entry.getValue());
                 argArray[0] = arg;
-                final Object paramName = entry.getKey();
+                final String paramName = ((TemplateScalarModel) 
entry.getKey()).getAsString();
                 Method setterMethod = (Method) propertySetters.get(paramName);
                 if (setterMethod == null) {
                     if (dynaSetter == null) {
@@ -130,7 +130,8 @@ class JspTagModelBase {
         }
     }
 
-    protected final TemplateModelException 
toTemplateModelExceptionOrRethrow(Exception e) throws TemplateModelException {
+    protected final TemplateModelException 
toTemplateModelExceptionOrRethrow(Throwable e)
+            throws TemplateModelException {
         if (e instanceof RuntimeException && 
!isCommonRuntimeException((RuntimeException) e)) {
             throw (RuntimeException) e;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
index 0e01e46..810a50c 100644
--- 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java
@@ -22,7 +22,6 @@ package org.apache.freemarker.servlet.jsp;
 import java.beans.IntrospectionException;
 import java.io.IOException;
 import java.io.Writer;
-import java.util.Map;
 
 import javax.servlet.jsp.JspContext;
 import javax.servlet.jsp.JspException;
@@ -33,15 +32,22 @@ import javax.servlet.jsp.tagext.Tag;
 
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
 
 /**
  * Adapts a {@link SimpleTag}-based custom JSP tag to be a value that's 
callable in templates as an user-defined
- * directive. For {@link Tag}-based custom JSP tags {@link TagTransformModel} 
is used instead.
+ * directive. For {@link Tag}-based custom JSP tags {@link TagDirectiveModel} 
is used instead.
  */
 class SimpleTagDirectiveModel extends JspTagModelBase implements 
TemplateDirectiveModel {
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = 
ArgumentArrayLayout.create(
+            0, false,
+            null, true);
+
     protected SimpleTagDirectiveModel(String tagName, Class tagClass) throws 
IntrospectionException {
         super(tagName, tagClass);
         if (!SimpleTag.class.isAssignableFrom(tagClass)) {
@@ -53,9 +59,8 @@ class SimpleTagDirectiveModel extends JspTagModelBase 
implements TemplateDirecti
     }
 
     @Override
-    public void execute(Environment env, Map args, TemplateModel[] outArgs,
-                        final TemplateDirectiveBody body)
-    throws TemplateException, IOException {
+    public void execute(TemplateModel[] args, final CallPlace callPlace, 
Writer out, final Environment env)
+            throws TemplateException, IOException {
         try {
             SimpleTag tag = (SimpleTag) getTagInstance();
             final FreeMarkerPageContext pageContext = 
PageContextFactory.getCurrentPageContext();
@@ -66,8 +71,9 @@ class SimpleTagDirectiveModel extends JspTagModelBase 
implements TemplateDirecti
                 if (parentTag != null) {
                     tag.setParent(parentTag);
                 }
-                setupTag(tag, args, pageContext.getObjectWrapper());
-                if (body != null) {
+                setupTag(tag, (TemplateHashModelEx2) 
args[ARGS_LAYOUT.getNamedVarargsArgumentIndex()],
+                        pageContext.getObjectWrapper());
+                if (callPlace.hasNestedContent()) {
                     tag.setJspBody(new JspFragment() {
                         @Override
                         public JspContext getJspContext() {
@@ -77,7 +83,7 @@ class SimpleTagDirectiveModel extends JspTagModelBase 
implements TemplateDirecti
                         @Override
                         public void invoke(Writer out) throws JspException, 
IOException {
                             try {
-                                body.render(out == null ? pageContext.getOut() 
: out);
+                                callPlace.executeNestedContent(null, out == 
null ? pageContext.getOut() : out, env);
                             } catch (TemplateException e) {
                                 throw new 
TemplateExceptionWrapperJspException(e);
                             }
@@ -99,7 +105,12 @@ class SimpleTagDirectiveModel extends JspTagModelBase 
implements TemplateDirecti
             throw toTemplateModelExceptionOrRethrow(e);
         }
     }
-    
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
     static final class TemplateExceptionWrapperJspException extends 
JspException {
 
         public TemplateExceptionWrapperJspException(Throwable cause) {

Reply via email to