http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
new file mode 100644
index 0000000..661046f
--- /dev/null
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagDirectiveModel.java
@@ -0,0 +1,256 @@
+/*
+ * 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.beans.IntrospectionException;
+import java.io.IOException;
+import java.io.Writer;
+
+import javax.servlet.jsp.JspException;
+import javax.servlet.jsp.JspWriter;
+import javax.servlet.jsp.tagext.BodyTag;
+import javax.servlet.jsp.tagext.IterationTag;
+import javax.servlet.jsp.tagext.SimpleTag;
+import javax.servlet.jsp.tagext.Tag;
+import javax.servlet.jsp.tagext.TryCatchFinally;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.TemplateException;
+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;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Adapts a {@link Tag}-based custom JSP tag to be a value that's callable in 
templates as an user-defined directive.
+ * For {@link SimpleTag}-based custom JSP tags {@link SimpleTagDirectiveModel} 
is used instead.
+ */
+class TagDirectiveModel extends JspTagModelBase implements 
TemplateDirectiveModel {
+    private static final Logger LOG = 
LoggerFactory.getLogger(TagDirectiveModel.class);
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = 
ArgumentArrayLayout.create(
+            0, false,
+            null, true);
+
+    private final boolean isBodyTag;
+    private final boolean isIterationTag;
+    private final boolean isTryCatchFinally;
+
+    public TagDirectiveModel(String tagName, Class tagClass) throws 
IntrospectionException {
+        super(tagName, tagClass);
+        isIterationTag = IterationTag.class.isAssignableFrom(tagClass);
+        isBodyTag = isIterationTag && BodyTag.class.isAssignableFrom(tagClass);
+        isTryCatchFinally = TryCatchFinally.class.isAssignableFrom(tagClass);
+    }
+
+    @Override
+    public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, 
Environment env)
+            throws TemplateException, IOException {
+        try {
+            Tag tag = (Tag) getTagInstance();
+            FreeMarkerPageContext pageContext = 
PageContextFactory.getCurrentPageContext();
+            Tag parentTag = (Tag) pageContext.peekTopTag(Tag.class);
+            tag.setParent(parentTag);
+            tag.setPageContext(pageContext);
+            setupTag(tag, (TemplateHashModelEx2) 
args[ARGS_LAYOUT.getNamedVarargsArgumentIndex()],
+                    pageContext.getObjectWrapper());
+            // If the parent of this writer is not a JspWriter itself, use
+            // a little Writer-to-JspWriter adapter...
+            boolean usesAdapter;
+            if (out instanceof JspWriter) {
+                // This is just a sanity check. If it were JDK 1.4-only,
+                // we'd use an assert.
+                if (out != pageContext.getOut()) {
+                    throw new TemplateModelException(
+                        "out != pageContext.getOut(). Out is " + 
+                        out + " pageContext.getOut() is " +
+                        pageContext.getOut());
+                }
+                usesAdapter = false;
+            } else {                
+                out = new JspWriterAdapter(out);
+                pageContext.pushWriter((JspWriter) out);
+                usesAdapter = true;
+            }
+
+            // TODO [FM3] In FM2 this was done with a TemplateTransformModel, 
which has returned a Writer that
+            // encapsulated the logic. See if there's a better solution now 
that we use the redesigned
+            // TemplateDirectiveModel.
+            TagBodyContent bodyContent = new TagBodyContent(out, tag, 
pageContext, usesAdapter);
+            pageContext.pushTopTag(tag);
+            pageContext.pushWriter(bodyContent);
+            try {
+                if (bodyContent.doStartTag()) {
+                    do {
+                        callPlace.executeNestedContent(null, bodyContent, env);
+                    } while (bodyContent.doAfterBody());
+                }
+            } catch (Throwable e) {
+                bodyContent.doCatch(e);
+            } finally {
+                bodyContent.close(); // Pops `topTag` and `writer`
+            }
+        } catch (Throwable e) {
+            throw toTemplateModelExceptionOrRethrow(e);
+        }
+    }
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
+
+    /**
+     * Implements extra methods to help mimicking JSP container behavior 
around the
+     * {@link TemplateDirectiveModel#execute(TemplateModel[], CallPlace, 
Writer, Environment)} call.
+     */
+    class TagBodyContent extends BodyContentImpl {
+        private final Tag tag;
+        private final FreeMarkerPageContext pageContext;
+        private boolean needPop = true;
+        private final boolean needDoublePop;
+
+        TagBodyContent(Writer out, Tag tag, FreeMarkerPageContext pageContext, 
boolean needDoublePop) {
+            super((JspWriter) out, false);
+            this.needDoublePop = needDoublePop;
+            this.tag = tag;
+            this.pageContext = pageContext;
+        }
+        
+        @Override
+        public String toString() {
+            return "TagBodyContent for " + tag.getClass().getName() + " 
wrapping a " + getEnclosingWriter().toString();
+        }
+
+        Tag getTag() {
+            return tag;
+        }
+        
+        FreeMarkerPageContext getPageContext() {
+            return pageContext;
+        }
+
+        /**
+         * @return Whether to execute the nested content (the body, with JSP 
terminology)
+         */
+        private boolean doStartTag() throws TemplateModelException {
+            try {
+                int dst = tag.doStartTag();
+                switch(dst) {
+                    case Tag.SKIP_BODY:
+                    // EVAL_PAGE is illegal actually, but some taglibs out 
there
+                    // use it, and it seems most JSP compilers allow them to 
and
+                    // treat it identically to SKIP_BODY, so we're going with 
+                    // the flow and we allow it too, altough strictly speaking
+                    // it's in violation of the spec.
+                    case Tag.EVAL_PAGE: {
+                        endEvaluation();
+                        return false;
+                    }
+                    case BodyTag.EVAL_BODY_BUFFERED: {
+                        if (isBodyTag) {
+                            initBuffer();
+                            BodyTag btag = (BodyTag) tag;
+                            btag.setBodyContent(this);
+                            btag.doInitBody();
+                        } else {
+                            throw new TemplateModelException("Can't buffer 
body since " + tag.getClass().getName() + " does not implement BodyTag.");
+                        }
+                        // Intentional fall-through
+                    }
+                    case Tag.EVAL_BODY_INCLUDE: {
+                        return true;
+                    }
+                    default: {
+                        throw new RuntimeException("Illegal return value " + 
dst + " from " + tag.getClass().getName() + ".doStartTag()");
+                    }
+                }
+            } catch (Exception e) {
+                throw toTemplateModelExceptionOrRethrow(e);
+            }
+        }
+
+        /**
+         * @return Whether to execute the nested content again (the body, with 
JSP terminology)
+         */
+        private boolean doAfterBody() throws TemplateModelException {
+            try {
+                if (isIterationTag) {
+                    int dab = ((IterationTag) tag).doAfterBody();
+                    switch(dab) {
+                        case Tag.SKIP_BODY:
+                            endEvaluation();
+                            return false;
+                        case IterationTag.EVAL_BODY_AGAIN:
+                            return true;
+                        default:
+                            throw new TemplateModelException("Unexpected 
return value " + dab + "from " + tag.getClass().getName() + ".doAfterBody()");
+                    }
+                }
+                endEvaluation();
+                return false;
+            } catch (Exception e) {
+                throw toTemplateModelExceptionOrRethrow(e);
+            }
+        }
+        
+        private void endEvaluation() throws JspException {
+            if (needPop) {
+                pageContext.popWriter();
+                needPop = false;
+            }
+            if (tag.doEndTag() == Tag.SKIP_PAGE) {
+                LOG.warn("Tag.SKIP_PAGE was ignored from a {} tag.", 
tag.getClass().getName());
+            }
+        }
+
+        private void doCatch(Throwable t) throws Throwable {
+            if (isTryCatchFinally) {
+                ((TryCatchFinally) tag).doCatch(t);
+            } else {
+                throw t;
+            }
+        }
+
+        @Override
+        public void close() {
+            if (needPop) {
+                pageContext.popWriter();
+            }
+            pageContext.popTopTag();
+            try {
+                if (isTryCatchFinally) {
+                    ((TryCatchFinally) tag).doFinally();
+                }
+                // No pooling yet
+                tag.release();
+            } finally {
+                if (needDoublePop) {
+                    pageContext.popWriter();
+                }
+            }
+        }
+
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagTransformModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagTransformModel.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagTransformModel.java
deleted file mode 100644
index ce7b040..0000000
--- 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TagTransformModel.java
+++ /dev/null
@@ -1,419 +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.servlet.jsp;
-
-import java.beans.IntrospectionException;
-import java.io.CharArrayReader;
-import java.io.CharArrayWriter;
-import java.io.IOException;
-import java.io.Reader;
-import java.io.Writer;
-import java.util.Map;
-
-import javax.servlet.jsp.JspException;
-import javax.servlet.jsp.JspWriter;
-import javax.servlet.jsp.tagext.BodyContent;
-import javax.servlet.jsp.tagext.BodyTag;
-import javax.servlet.jsp.tagext.IterationTag;
-import javax.servlet.jsp.tagext.SimpleTag;
-import javax.servlet.jsp.tagext.Tag;
-import javax.servlet.jsp.tagext.TryCatchFinally;
-
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateTransformModel;
-import org.apache.freemarker.core.model.TransformControl;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-
-/**
- * Adapts a {@link Tag}-based custom JSP tag to be a value that's callable in 
templates as an user-defined directive.
- * For {@link SimpleTag}-based custom JSP tags {@link SimpleTagDirectiveModel} 
is used instead.
- */
-class TagTransformModel extends JspTagModelBase implements 
TemplateTransformModel {
-    private static final Logger LOG = 
LoggerFactory.getLogger(TagTransformModel.class);
-    
-    private final boolean isBodyTag;
-    private final boolean isIterationTag;
-    private final boolean isTryCatchFinally;
-            
-    public TagTransformModel(String tagName, Class tagClass) throws 
IntrospectionException {
-        super(tagName, tagClass);
-        isIterationTag = IterationTag.class.isAssignableFrom(tagClass);
-        isBodyTag = isIterationTag && BodyTag.class.isAssignableFrom(tagClass);
-        isTryCatchFinally = TryCatchFinally.class.isAssignableFrom(tagClass);
-    }
-    
-    @Override
-    public Writer getWriter(Writer out, Map args) throws 
TemplateModelException {
-        try {
-            Tag tag = (Tag) getTagInstance();
-            FreeMarkerPageContext pageContext = 
PageContextFactory.getCurrentPageContext();
-            Tag parentTag = (Tag) pageContext.peekTopTag(Tag.class);
-            tag.setParent(parentTag);
-            tag.setPageContext(pageContext);
-            setupTag(tag, args, pageContext.getObjectWrapper());
-            // If the parent of this writer is not a JspWriter itself, use
-            // a little Writer-to-JspWriter adapter...
-            boolean usesAdapter;
-            if (out instanceof JspWriter) {
-                // This is just a sanity check. If it were JDK 1.4-only,
-                // we'd use an assert.
-                if (out != pageContext.getOut()) {
-                    throw new TemplateModelException(
-                        "out != pageContext.getOut(). Out is " + 
-                        out + " pageContext.getOut() is " +
-                        pageContext.getOut());
-                }
-                usesAdapter = false;
-            } else {                
-                out = new JspWriterAdapter(out);
-                pageContext.pushWriter((JspWriter) out);
-                usesAdapter = true;
-            }
-            JspWriter w = new TagWriter(out, tag, pageContext, usesAdapter);
-            pageContext.pushTopTag(tag);
-            pageContext.pushWriter(w);
-            return w;
-        } catch (Exception e) {
-            throw toTemplateModelExceptionOrRethrow(e);
-        }
-    }
-
-    /**
-     * An implementation of BodyContent that buffers it's input to a char[].
-     */
-    static 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);
-        }
-
-    }
-
-    class TagWriter extends BodyContentImpl implements TransformControl {
-        private final Tag tag;
-        private final FreeMarkerPageContext pageContext;
-        private boolean needPop = true;
-        private final boolean needDoublePop;
-        
-        TagWriter(Writer out, Tag tag, FreeMarkerPageContext pageContext, 
boolean needDoublePop) {
-            super((JspWriter) out, false);
-            this.needDoublePop = needDoublePop;
-            this.tag = tag;
-            this.pageContext = pageContext;
-        }
-        
-        @Override
-        public String toString() {
-            return "TagWriter for " + tag.getClass().getName() + " wrapping a 
" + getEnclosingWriter().toString();
-        }
-
-        Tag getTag() {
-            return tag;
-        }
-        
-        FreeMarkerPageContext getPageContext() {
-            return pageContext;
-        }
-        
-        @Override
-        public int onStart()
-        throws TemplateModelException {
-            try {
-                int dst = tag.doStartTag();
-                switch(dst) {
-                    case Tag.SKIP_BODY:
-                    // EVAL_PAGE is illegal actually, but some taglibs out 
there
-                    // use it, and it seems most JSP compilers allow them to 
and
-                    // treat it identically to SKIP_BODY, so we're going with 
-                    // the flow and we allow it too, altough strictly speaking
-                    // it's in violation of the spec.
-                    case Tag.EVAL_PAGE: {
-                        endEvaluation();
-                        return TransformControl.SKIP_BODY;
-                    }
-                    case BodyTag.EVAL_BODY_BUFFERED: {
-                        if (isBodyTag) {
-                            initBuffer();
-                            BodyTag btag = (BodyTag) tag;
-                            btag.setBodyContent(this);
-                            btag.doInitBody();
-                        } else {
-                            throw new TemplateModelException("Can't buffer 
body since " + tag.getClass().getName() + " does not implement BodyTag.");
-                        }
-                        // Intentional fall-through
-                    }
-                    case Tag.EVAL_BODY_INCLUDE: {
-                        return TransformControl.EVALUATE_BODY;
-                    }
-                    default: {
-                        throw new RuntimeException("Illegal return value " + 
dst + " from " + tag.getClass().getName() + ".doStartTag()");
-                    }
-                }
-            } catch (Exception e) {
-                throw toTemplateModelExceptionOrRethrow(e);
-            }
-        }
-        
-        @Override
-        public int afterBody()
-        throws TemplateModelException {
-            try {
-                if (isIterationTag) {
-                    int dab = ((IterationTag) tag).doAfterBody();
-                    switch(dab) {
-                        case Tag.SKIP_BODY:
-                            endEvaluation();
-                            return END_EVALUATION;
-                        case IterationTag.EVAL_BODY_AGAIN:
-                            return REPEAT_EVALUATION;
-                        default:
-                            throw new TemplateModelException("Unexpected 
return value " + dab + "from " + tag.getClass().getName() + ".doAfterBody()");
-                    }
-                }
-                endEvaluation();
-                return END_EVALUATION;
-            } catch (Exception e) {
-                throw toTemplateModelExceptionOrRethrow(e);
-            }
-        }
-        
-        private void endEvaluation() throws JspException {
-            if (needPop) {
-                pageContext.popWriter();
-                needPop = false;
-            }
-            if (tag.doEndTag() == Tag.SKIP_PAGE) {
-                LOG.warn("Tag.SKIP_PAGE was ignored from a {} tag.", 
tag.getClass().getName());
-            }
-        }
-        
-        @Override
-        public void onError(Throwable t) throws Throwable {
-            if (isTryCatchFinally) {
-                ((TryCatchFinally) tag).doCatch(t);
-            } else {
-                throw t;
-            }
-        }
-        
-        @Override
-        public void close() {
-            if (needPop) {
-                pageContext.popWriter();
-            }
-            pageContext.popTopTag();
-            try {
-                if (isTryCatchFinally) {
-                    ((TryCatchFinally) tag).doFinally();
-                }
-                // No pooling yet
-                tag.release();
-            } finally {
-                if (needDoublePop) {
-                    pageContext.popWriter();
-                }
-            }
-        }
-        
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
----------------------------------------------------------------------
diff --git 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
index cacb22b..606cef8 100644
--- 
a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
+++ 
b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/TaglibFactory.java
@@ -64,11 +64,11 @@ import javax.xml.parsers.SAXParserFactory;
 import org.apache.freemarker.core.ConfigurationException;
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 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.DefaultObjectWrapper;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.CommonBuilder;
@@ -232,7 +232,7 @@ public class TaglibFactory implements TemplateHashModel {
      *            to integrate JSP taglib support should do the same.
      * 
      * @return a {@link TemplateHashModel} representing the JSP taglib. Each 
element of this hash represents a single
-     *         custom tag or EL function from the library, implemented as a 
{@link TemplateTransformModel} or
+     *         custom tag or EL function from the library, implemented as a 
{@link TemplateDirectiveModel} or
      *         {@link TemplateMethodModelEx}, respectively.
      */
     @Override
@@ -1762,7 +1762,7 @@ public class TaglibFactory implements TemplateHashModel {
                     final TemplateModel customTagModel;
                     try {
                         if (Tag.class.isAssignableFrom(tagClass)) {
-                            customTagModel = new 
TagTransformModel(tagNameCData, tagClass);
+                            customTagModel = new 
TagDirectiveModel(tagNameCData, tagClass);
                         } else {
                             customTagModel = new 
SimpleTagDirectiveModel(tagNameCData, tagClass);
                         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
----------------------------------------------------------------------
diff --git 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
index 22f4f27..0a9e9cd 100644
--- 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
+++ 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertDirective.java
@@ -20,43 +20,40 @@
 package org.apache.freemarker.test.templateutil;
 
 import java.io.IOException;
-import java.util.Map;
+import java.io.Writer;
 
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.NestedContentNotSupportedException;
 import org.apache.freemarker.core.TemplateException;
+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.util.FTLUtil;
+import org.apache.freemarker.core.util.StringToIndexMap;
 
 public class AssertDirective implements TemplateDirectiveModel {
-
     public static AssertDirective INSTANCE = new AssertDirective();
-    
-    private static final String TEST_PARAM = "test";
-    
+
+    private static final String TEST_ARG_NAME = "test";
+    private static final int TEST_ARG_IDX = 0;
+    private static final StringToIndexMap ARG_NAMES_TO_IDX = 
StringToIndexMap.of(TEST_ARG_NAME, TEST_ARG_IDX);
+    private static final ArgumentArrayLayout ARGS_LAYOUT = 
ArgumentArrayLayout.create(
+            0, false,
+            ARG_NAMES_TO_IDX, false);
+
     private AssertDirective() { }
     
     @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 {
-        TemplateModel test = null;
-        for (Object paramEnt  : params.entrySet()) {
-            Map.Entry<String, TemplateModel> param = (Map.Entry) paramEnt;
-            String paramName = param.getKey();
-            if (paramName.equals(TEST_PARAM)) {
-                test = param.getValue();
-            } else {
-                throw new UnsupportedParameterException(paramName, env);
-            }
-        }
+        NestedContentNotSupportedException.check(callPlace);
+
+        TemplateModel test = args[TEST_ARG_IDX];
         if (test == null) {
-            throw new MissingRequiredParameterException(TEST_PARAM, env);
+            throw new MissingRequiredParameterException(TEST_ARG_NAME, env);
         }
-        NestedContentNotSupportedException.check(body);
-        
         if (!(test instanceof TemplateBooleanModel)) {
             throw new AssertationFailedInTemplateException("Assertion 
failed:\n"
                     + "The value had to be boolean, but it was of type" + 
FTLUtil.getTypeDescription(test),
@@ -67,7 +64,10 @@ public class AssertDirective implements 
TemplateDirectiveModel {
                     + "the value was false.",
                     env);
         }
-        
     }
 
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
----------------------------------------------------------------------
diff --git 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
index 9df8992..a02308f 100644
--- 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
+++ 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertEqualsDirective.java
@@ -20,72 +20,79 @@
 package org.apache.freemarker.test.templateutil;
 
 import java.io.IOException;
-import java.util.Map;
+import java.io.Writer;
 
 import org.apache.freemarker.core.Environment;
 import org.apache.freemarker.core.NestedContentNotSupportedException;
 import org.apache.freemarker.core.TemplateException;
+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.TemplateDateModel;
-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.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._StringUtil;
 
 public class AssertEqualsDirective implements TemplateDirectiveModel {
     
     public static AssertEqualsDirective INSTANCE = new AssertEqualsDirective();
 
-    private static final String ACTUAL_PARAM = "actual";
-    private static final String EXPECTED_PARAM = "expected";
+    private static final int ACTUAL_ARG_IDX = 0;
+    private static final int EXPECTED_ARG_IDX = 1;
+
+    private static final String ACTUAL_ARG_NAME = "actual";
+    private static final String EXPECTED_ARG_NAME = "expected";
+
+    private static final StringToIndexMap ARG_NAME_TO_IDX = 
StringToIndexMap.of(
+            ACTUAL_ARG_NAME, ACTUAL_ARG_IDX,
+            EXPECTED_ARG_NAME, EXPECTED_ARG_IDX);
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = 
ArgumentArrayLayout.create(
+            0, false,
+            ARG_NAME_TO_IDX, false);
 
     private AssertEqualsDirective() { }
     
     @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 {
-        TemplateModel actual = null;
-        TemplateModel expected = null;
-        for (Object paramEnt  : params.entrySet()) {
-            Map.Entry<String, TemplateModel> param = (Map.Entry) paramEnt;
-            String paramName = param.getKey();
-            if (paramName.equals(ACTUAL_PARAM)) {
-                actual = param.getValue();
-            } else if (paramName.equals(EXPECTED_PARAM)) {
-                expected = param.getValue();
-            } else {
-                throw new UnsupportedParameterException(paramName, env);
-            }
-        }
+        NestedContentNotSupportedException.check(callPlace);
+
+        TemplateModel actual = args[ACTUAL_ARG_IDX];
         if (actual == null) {
-            throw new MissingRequiredParameterException(ACTUAL_PARAM, env);
+            throw new MissingRequiredParameterException(ACTUAL_ARG_NAME, env);
         }
+
+        TemplateModel expected = args[EXPECTED_ARG_IDX];
         if (expected == null) {
-            throw new MissingRequiredParameterException(EXPECTED_PARAM, env);
+            throw new MissingRequiredParameterException(EXPECTED_ARG_NAME, 
env);
         }
-        NestedContentNotSupportedException.check(body);
-        
+
         if (!env.applyEqualsOperatorLenient(actual, expected)) {
             throw new AssertationFailedInTemplateException("Assertion 
failed:\n"
-                    + "Expected: " + tryUnwrapp(expected) + "\n"
-                    + "Actual: " + tryUnwrapp(actual),
+                    + "Expected: " + tryUnwrap(expected) + "\n"
+                    + "Actual: " + tryUnwrap(actual),
                     env);
         }
-        
     }
 
-    private String tryUnwrapp(TemplateModel value) throws 
TemplateModelException {
+    private String tryUnwrap(TemplateModel value) throws 
TemplateModelException {
         if (value == null) return "null";
-        // This is the same order as comparison goes:
+            // This is the same order as comparison goes:
         else if (value instanceof TemplateNumberModel) return 
((TemplateNumberModel) value).getAsNumber().toString();
         else if (value instanceof TemplateDateModel) return 
((TemplateDateModel) value).getAsDate().toString();
         else if (value instanceof TemplateScalarModel) return 
_StringUtil.jQuote(((TemplateScalarModel) value).getAsString());
         else if (value instanceof TemplateBooleanModel) return 
String.valueOf(((TemplateBooleanModel) value).getAsBoolean());
-        // This shouldn't be reached, as the comparison should have failed 
earlier:
+            // This shouldn't be reached, as the comparison should have failed 
earlier:
         else return value.toString();
     }
 
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
----------------------------------------------------------------------
diff --git 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
index 4bb0721..effc0e7 100644
--- 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
+++ 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/AssertFailsDirective.java
@@ -20,107 +20,110 @@
 package org.apache.freemarker.test.templateutil;
 
 import java.io.IOException;
-import java.util.Map;
+import java.io.Writer;
 import java.util.regex.Pattern;
 
 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.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._NullWriter;
 import org.apache.freemarker.core.util._StringUtil;
 
 public class AssertFailsDirective implements TemplateDirectiveModel {
-    
+
     public static AssertFailsDirective INSTANCE = new AssertFailsDirective();
 
-    private static final String MESSAGE_PARAM = "message";
-    private static final String MESSAGE_REGEXP_PARAM = "messageRegexp";
-    private static final String EXCEPTION_PARAM = "exception";
-    private static final String CAUSE_NESTING_LEVEL_PARAM = 
"causeNestingLevel";
-    
-    private AssertFailsDirective() { }
+    private static final int MESSAGE_ARG_IDX = 0;
+    private static final int MESSAGE_REGEXP_ARG_IDX = 1;
+    private static final int EXCEPTION_ARG_IDX = 2;
+    private static final int CAUSE_NESTING_LEVEL_ARG_IDX = 3;
 
-    @Override
-    public void execute(Environment env, Map params, TemplateModel[] loopVars, 
TemplateDirectiveBody body)
+    private static final String MESSAGE_ARG_NAME = "message";
+    private static final String MESSAGE_REGEXP_ARG_NAME = "messageRegexp";
+    private static final String EXCEPTION_ARG_NAME = "exception";
+    private static final String CAUSE_NESTING_LEVEL_ARG_NAME = 
"causeNestingLevel";
+
+    private static final StringToIndexMap ARG_NAME_TO_IDX = 
StringToIndexMap.of(
+            MESSAGE_ARG_NAME, MESSAGE_ARG_IDX,
+            MESSAGE_REGEXP_ARG_NAME, MESSAGE_REGEXP_ARG_IDX,
+            EXCEPTION_ARG_NAME, EXCEPTION_ARG_IDX,
+            CAUSE_NESTING_LEVEL_ARG_NAME, CAUSE_NESTING_LEVEL_ARG_IDX);
+
+    private static final ArgumentArrayLayout ARGS_LAYOUT = 
ArgumentArrayLayout.create(
+            0, false,
+            ARG_NAME_TO_IDX, false);
+
+    private AssertFailsDirective() {
+    }
+
+    public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, 
Environment env)
             throws TemplateException, IOException {
-        String message = null;
-        Pattern messageRegexp = null;
-        String exception = null;
-        int causeNestingLevel = 0;
-        for (Object paramEnt  : params.entrySet()) {
-            Map.Entry<String, TemplateModel> param = (Map.Entry) paramEnt;
-            String paramName = param.getKey();
-            if (paramName.equals(MESSAGE_PARAM)) {
-                message = getAsString(param.getValue(), MESSAGE_PARAM, env);
-            } else if (paramName.equals(MESSAGE_REGEXP_PARAM)) {
-                messageRegexp = Pattern.compile(
-                        getAsString(param.getValue(), MESSAGE_REGEXP_PARAM, 
env),
-                        Pattern.CASE_INSENSITIVE);
-            } else if (paramName.equals(EXCEPTION_PARAM)) {
-                exception = getAsString(param.getValue(), EXCEPTION_PARAM, 
env);
-            } else if (paramName.equals(CAUSE_NESTING_LEVEL_PARAM)) {
-                causeNestingLevel = getAsInt(param.getValue(), 
CAUSE_NESTING_LEVEL_PARAM, env);
-            } else {
-                throw new UnsupportedParameterException(paramName, env);
-            }
-        }
-        
-        if (body != null) {
+        String message = getAsString(args[MESSAGE_ARG_IDX], MESSAGE_ARG_NAME, 
env);
+        Pattern messageRegexp = getAsPattern(args[MESSAGE_REGEXP_ARG_IDX], 
MESSAGE_REGEXP_ARG_NAME, env);
+        String exception = getAsString(args[EXCEPTION_ARG_IDX], 
EXCEPTION_ARG_NAME, env);
+        int causeNestingLevel = getAsInt(args[CAUSE_NESTING_LEVEL_ARG_IDX], 0, 
CAUSE_NESTING_LEVEL_ARG_NAME, env);
+        if (callPlace.hasNestedContent()) {
             boolean blockFailed;
             try {
-                body.render(_NullWriter.INSTANCE);
+                callPlace.executeNestedContent(null, _NullWriter.INSTANCE, 
env);
                 blockFailed = false;
             } catch (Throwable e) {
                 blockFailed = true;
-                
-                int causeNestingLevelCountDown = causeNestingLevel; 
+
+                int causeNestingLevelCountDown = causeNestingLevel;
                 while (causeNestingLevelCountDown != 0) {
                     e = e.getCause();
                     if (e == null) {
                         throw new AssertationFailedInTemplateException(
                                 "Failure is not like expected: The cause 
exception nesting dept was lower than "
-                                + causeNestingLevel + ".",
+                                        + causeNestingLevel + ".",
                                 env);
                     }
                     causeNestingLevelCountDown--;
                 }
-                
+
                 if (message != null || messageRegexp != null) {
                     if (e.getMessage() == null) {
                         throw new AssertationFailedInTemplateException(
                                 "Failure is not like expected. The exception 
message was null, "
-                                + "and thus it doesn't contain:\n" + 
_StringUtil.jQuote(message) + ".",
+                                        + "and thus it doesn't contain:\n" + 
_StringUtil.jQuote(message) + ".",
                                 env);
                     }
                     if (message != null) {
                         String actualMessage = e instanceof TemplateException
                                 ? ((TemplateException) 
e).getMessageWithoutStackTop() : e.getMessage();
-                        if 
(actualMessage.toLowerCase().indexOf(message.toLowerCase()) == -1) {
+                        if 
(!actualMessage.toLowerCase().contains(message.toLowerCase())) {
                             throw new AssertationFailedInTemplateException(
-                                    "Failure is not like expected. The 
exception message:\n" + _StringUtil.jQuote(actualMessage)
-                                    + "\ndoesn't contain:\n" + 
_StringUtil.jQuote(message) + ".",
+                                    "Failure is not like expected. The 
exception message:\n" + _StringUtil
+                                            .jQuote(actualMessage)
+                                            + "\ndoesn't contain:\n" + 
_StringUtil.jQuote(message) + ".",
                                     env);
                         }
                     }
                     if (messageRegexp != null) {
                         if (!messageRegexp.matcher(e.getMessage()).find()) {
                             throw new AssertationFailedInTemplateException(
-                                    "Failure is not like expected. The 
exception message:\n" + _StringUtil.jQuote(e.getMessage())
-                                    + "\ndoesn't match this regexp:\n" + 
_StringUtil.jQuote(messageRegexp.toString())
-                                    + ".",
+                                    "Failure is not like expected. The 
exception message:\n" + _StringUtil
+                                            .jQuote(e.getMessage())
+                                            + "\ndoesn't match this regexp:\n" 
+ _StringUtil
+                                            .jQuote(messageRegexp.toString())
+                                            + ".",
                                     env);
                         }
                     }
                 }
-                if (exception != null && 
e.getClass().getName().indexOf(exception) == -1) {
+                if (exception != null && 
!e.getClass().getName().contains(exception)) {
                     throw new AssertationFailedInTemplateException(
-                            "Failure is not like expected. The exception class 
name " + _StringUtil.jQuote(e.getClass().getName())
-                            + " doesn't contain " + 
_StringUtil.jQuote(message) + ".",
+                            "Failure is not like expected. The exception class 
name " + _StringUtil
+                                    .jQuote(e.getClass().getName())
+                                    + " doesn't contain " + 
_StringUtil.jQuote(message) + ".",
                             env);
                 }
             }
@@ -134,19 +137,37 @@ public class AssertFailsDirective implements 
TemplateDirectiveModel {
 
     private String getAsString(TemplateModel value, String paramName, 
Environment env)
             throws BadParameterTypeException, TemplateModelException {
+        if (value == null) {
+            return null;
+        }
         if (value instanceof TemplateScalarModel) {
-            return ((TemplateScalarModel) value).getAsString(); 
+            return ((TemplateScalarModel) value).getAsString();
         } else {
             throw new BadParameterTypeException(paramName, "string", value, 
env);
         }
     }
 
-    private int getAsInt(TemplateModel value, String paramName, Environment 
env) throws BadParameterTypeException, TemplateModelException {
+    private Pattern getAsPattern(TemplateModel value, String paramName, 
Environment env)
+            throws TemplateModelException, BadParameterTypeException {
+        String s = getAsString(value, paramName, env);
+        return s != null ? Pattern.compile(s, Pattern.CASE_INSENSITIVE) : null;
+    }
+
+    private int getAsInt(TemplateModel value, int defaultValue, String 
paramName, Environment env) throws
+            BadParameterTypeException,
+            TemplateModelException {
+        if (value == null) {
+            return defaultValue;
+        }
         if (value instanceof TemplateNumberModel) {
             return ((TemplateNumberModel) value).getAsNumber().intValue(); 
         } else {
             throw new BadParameterTypeException(paramName, "number", value, 
env);
         }
     }
-    
+
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ARGS_LAYOUT;
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
----------------------------------------------------------------------
diff --git 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
index 6dbf8f5..b027974 100644
--- 
a/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
+++ 
b/freemarker-test-utils/src/main/java/org/apache/freemarker/test/templateutil/NoOutputDirective.java
@@ -20,14 +20,14 @@
 package org.apache.freemarker.test.templateutil;
 
 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.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.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.util._NullWriter;
 
 public class NoOutputDirective implements TemplateDirectiveModel {
@@ -39,12 +39,13 @@ public class NoOutputDirective implements 
TemplateDirectiveModel {
     }
 
     @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 {
-        if (!params.isEmpty()) {
-            throw new TemplateModelException("This directivey doesn't support 
any parameters.");
-        }
-        body.render(_NullWriter.INSTANCE);
+        callPlace.executeNestedContent(null, _NullWriter.INSTANCE, env);
     }
 
+    @Override
+    public ArgumentArrayLayout getArgumentArrayLayout() {
+        return ArgumentArrayLayout.PARAMETERLESS;
+    }
 }

Reply via email to