freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject [10/21] incubator-freemarker git commit: FREEMARKER-63: Removed TemplateTransformModel and the old TemplateDirectiveModel, renamed TemplateDirectiveModel2 to TemplateDirectiveModel. Removed the temporary `<~...>` syntax; now `<@...>` is used to call the
Date Mon, 07 Aug 2017 22:32:12 GMT
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java
deleted file mode 100644
index 931280c..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirUserDefined.java
+++ /dev/null
@@ -1,344 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import java.io.IOException;
-import java.lang.ref.Reference;
-import java.lang.ref.SoftReference;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-
-import org.apache.freemarker.core.model.TemplateDirectiveModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateTransformModel;
-import org.apache.freemarker.core.util.CommonSupplier;
-import org.apache.freemarker.core.util._StringUtil;
-
-import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
-
-/**
- * AST directive node: {@code <@exp .../>} or {@code <@exp ...>...</@...>}. Calls an user-defined directive (like a
- * macro).
- */
-// TODO [FM3][CF] Remove
-final class ASTDirUserDefined extends ASTDirective implements DirectiveCallPlace {
-
-    private ASTExpression nameExp;
-    private Map namedArgs;
-    private List positionalArgs, bodyParameterNames;
-    private transient volatile SoftReference/*List<Map.Entry<String,ASTExpression>>*/ sortedNamedArgsCache;
-    private CustomDataHolder customDataHolder;
-
-    ASTDirUserDefined(ASTExpression nameExp,
-         Map namedArgs,
-         TemplateElements children,
-         List bodyParameterNames) {
-        this.nameExp = nameExp;
-        this.namedArgs = namedArgs;
-        setChildren(children);
-        this.bodyParameterNames = bodyParameterNames;
-    }
-
-    ASTDirUserDefined(ASTExpression nameExp,
-         List positionalArgs,
-         TemplateElements children,
-         List bodyParameterNames) {
-        this.nameExp = nameExp;
-        this.positionalArgs = positionalArgs;
-        setChildren(children);
-        this.bodyParameterNames = bodyParameterNames;
-    }
-
-    @Override
-    ASTElement[] accept(Environment env) throws TemplateException, IOException {
-        TemplateModel tm = nameExp.eval(env);
-        if (tm == ASTDirMacro.DO_NOTHING_MACRO) return null; // shortcut here.
-        if (tm instanceof ASTDirMacro) {
-            ASTDirMacro macro = (ASTDirMacro) tm;
-            if (macro.isFunction()) {
-                throw new _MiscTemplateException(env,
-                        "Routine ", new _DelayedJQuote(macro.getName()), " is a function, not a directive. "
-                        + "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ",
-                        "<@someDirective someParam=f() />", ".");
-            }    
-            env.invoke(macro, namedArgs, positionalArgs, bodyParameterNames, getChildBuffer());
-        } else {
-            boolean isDirectiveModel = tm instanceof TemplateDirectiveModel; 
-            if (isDirectiveModel || tm instanceof TemplateTransformModel) {
-                Map args;
-                if (namedArgs != null && !namedArgs.isEmpty()) {
-                    args = new HashMap();
-                    for (Iterator it = namedArgs.entrySet().iterator(); it.hasNext(); ) {
-                        Map.Entry entry = (Map.Entry) it.next();
-                        String key = (String) entry.getKey();
-                        ASTExpression valueExp = (ASTExpression) entry.getValue();
-                        TemplateModel value = valueExp.eval(env);
-                        args.put(key, value);
-                    }
-                } else {
-                    args = Collections.emptyMap();
-                }
-                if (isDirectiveModel) {
-                    env.visit(getChildBuffer(), (TemplateDirectiveModel) tm, args, bodyParameterNames);
-                } else { 
-                    env.visitAndTransform(getChildBuffer(), (TemplateTransformModel) tm, args);
-                }
-            } else if (tm == null) {
-                throw InvalidReferenceException.getInstance(nameExp, env);
-            } else {
-                throw new NonUserDefinedDirectiveLikeException(nameExp, tm, env);
-            }
-        }
-        return null;
-    }
-
-    @Override
-    protected String dump(boolean canonical) {
-        StringBuilder sb = new StringBuilder();
-        if (canonical) sb.append('<');
-        sb.append('@');
-        MessageUtil.appendExpressionAsUntearable(sb, nameExp);
-        boolean nameIsInParen = sb.charAt(sb.length() - 1) == ')';
-        if (positionalArgs != null) {
-            for (int i = 0; i < positionalArgs.size(); i++) {
-                ASTExpression argExp = (ASTExpression) positionalArgs.get(i);
-                if (i != 0) {
-                    sb.append(',');
-                }
-                sb.append(' ');
-                sb.append(argExp.getCanonicalForm());
-            }
-        } else {
-            List entries = getSortedNamedArgs();
-            for (int i = 0; i < entries.size(); i++) {
-                Map.Entry entry = (Map.Entry) entries.get(i);
-                ASTExpression argExp = (ASTExpression) entry.getValue();
-                sb.append(' ');
-                sb.append(_StringUtil.toFTLTopLevelIdentifierReference((String) entry.getKey()));
-                sb.append('=');
-                MessageUtil.appendExpressionAsUntearable(sb, argExp);
-            }
-        }
-        if (bodyParameterNames != null && !bodyParameterNames.isEmpty()) {
-            sb.append("; ");
-            for (int i = 0; i < bodyParameterNames.size(); i++) {
-                if (i != 0) {
-                    sb.append(", ");
-                }
-                sb.append(_StringUtil.toFTLTopLevelIdentifierReference((String) bodyParameterNames.get(i)));
-            }
-        }
-        if (canonical) {
-            if (getChildCount() == 0) {
-                sb.append("/>");
-            } else {
-                sb.append('>');
-                sb.append(getChildrenCanonicalForm());
-                sb.append("</@");
-                if (!nameIsInParen
-                        && (nameExp instanceof ASTExpVariable
-                            || (nameExp instanceof ASTExpDot && ((ASTExpDot) nameExp).onlyHasIdentifiers()))) {
-                    sb.append(nameExp.getCanonicalForm());
-                }
-                sb.append('>');
-            }
-        }
-        return sb.toString();
-    }
-
-    @Override
-    String getASTNodeDescriptor() {
-        return "@";
-    }
-
-    @Override
-    int getParameterCount() {
-        return 1/*nameExp*/
-                + (positionalArgs != null ? positionalArgs.size() : 0)
-                + (namedArgs != null ? namedArgs.size() * 2 : 0)
-                + (bodyParameterNames != null ? bodyParameterNames.size() : 0);
-    }
-
-    @Override
-    Object getParameterValue(int idx) {
-        if (idx == 0) {
-            return nameExp;
-        } else {
-            int base = 1;
-            final int positionalArgsSize = positionalArgs != null ? positionalArgs.size() : 0;  
-            if (idx - base < positionalArgsSize) {
-                return positionalArgs.get(idx - base);
-            } else {
-                base += positionalArgsSize;
-                final int namedArgsSize = namedArgs != null ? namedArgs.size() : 0;
-                if (idx - base < namedArgsSize * 2) {
-                    Map.Entry namedArg = (Map.Entry) getSortedNamedArgs().get((idx - base) / 2);
-                    return (idx - base) % 2 == 0 ? namedArg.getKey() : namedArg.getValue();
-                } else {
-                    base += namedArgsSize * 2;
-                    final int bodyParameterNamesSize = bodyParameterNames != null ? bodyParameterNames.size() : 0;
-                    if (idx - base < bodyParameterNamesSize) {
-                        return bodyParameterNames.get(idx - base);
-                    } else {
-                        throw new IndexOutOfBoundsException();
-                    }
-                }
-            }
-        }
-    }
-
-    @Override
-    ParameterRole getParameterRole(int idx) {
-        if (idx == 0) {
-            return ParameterRole.CALLEE;
-        } else {
-            int base = 1;
-            final int positionalArgsSize = positionalArgs != null ? positionalArgs.size() : 0;  
-            if (idx - base < positionalArgsSize) {
-                return ParameterRole.ARGUMENT_VALUE;
-            } else {
-                base += positionalArgsSize;
-                final int namedArgsSize = namedArgs != null ? namedArgs.size() : 0;
-                if (idx - base < namedArgsSize * 2) {
-                    return (idx - base) % 2 == 0 ? ParameterRole.ARGUMENT_NAME : ParameterRole.ARGUMENT_VALUE;
-                } else {
-                    base += namedArgsSize * 2;
-                    final int bodyParameterNamesSize = bodyParameterNames != null ? bodyParameterNames.size() : 0;
-                    if (idx - base < bodyParameterNamesSize) {
-                        return ParameterRole.TARGET_LOOP_VARIABLE;
-                    } else {
-                        throw new IndexOutOfBoundsException();
-                    }
-                }
-            }
-        }
-    }
-    
-    /**
-     * Returns the named args by source-code order; it's not meant to be used during template execution, too slow for
-     * that!
-     */
-    private List/*<Map.Entry<String, ASTExpression>>*/ getSortedNamedArgs() {
-        Reference ref = sortedNamedArgsCache;
-        if (ref != null) {
-            List res = (List) ref.get();
-            if (res != null) return res;
-        }
-        
-        List res = MiscUtil.sortMapOfExpressions(namedArgs);
-        sortedNamedArgsCache = new SoftReference(res);
-        return res;
-    }
-
-    @Override
-    @SuppressFBWarnings(value={ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" }, justification="Performance tricks")
-    public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier supplier)
-            throws CallPlaceCustomDataInitializationException {
-        // We are using double-checked locking, utilizing Java memory model "final" trick.
-        // Note that this.customDataHolder is NOT volatile.
-        
-        CustomDataHolder customDataHolder = this.customDataHolder;  // Findbugs false alarm
-        if (customDataHolder == null) {  // Findbugs false alarm
-            synchronized (this) {
-                customDataHolder = this.customDataHolder;
-                if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) {
-                    customDataHolder = createNewCustomData(providerIdentity, supplier);
-                    this.customDataHolder = customDataHolder; 
-                }
-            }
-        }
-        
-        if (customDataHolder.providerIdentity != providerIdentity) {
-            synchronized (this) {
-                customDataHolder = this.customDataHolder;
-                if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) {
-                    customDataHolder = createNewCustomData(providerIdentity, supplier);
-                    this.customDataHolder = customDataHolder;
-                }
-            }
-        }
-        
-        return customDataHolder.customData;
-    }
-
-    private CustomDataHolder createNewCustomData(Object provierIdentity, CommonSupplier supplier)
-            throws CallPlaceCustomDataInitializationException {
-        CustomDataHolder customDataHolder;
-        Object customData;
-        try {
-            customData = supplier.get();
-        } catch (Exception e) {
-            throw new CallPlaceCustomDataInitializationException(
-                    "Failed to initialize custom data for provider identity "
-                    + _StringUtil.tryToString(provierIdentity) + " via factory "
-                    + _StringUtil.tryToString(supplier), e);
-        }
-        if (customData == null) {
-            throw new NullPointerException("CommonSupplier.get() has returned null");
-        }
-        customDataHolder = new CustomDataHolder(provierIdentity, customData);
-        return customDataHolder;
-    }
-
-    @Override
-    public boolean isNestedOutputCacheable() {
-        return isChildrenOutputCacheable();
-    }
-    
-/*
-    //REVISIT
-    boolean heedsOpeningWhitespace() {
-        return nestedBlock == null;
-    }
-
-    //REVISIT
-    boolean heedsTrailingWhitespace() {
-        return nestedBlock == null;
-    }*/
-    
-    /**
-     * Used for implementing double check locking in implementing the
-     * {@link DirectiveCallPlace#getOrCreateCustomData(Object, CommonSupplier)}.
-     */
-    private static class CustomDataHolder {
-        
-        private final Object providerIdentity;
-        private final Object customData;
-        public CustomDataHolder(Object providerIdentity, Object customData) {
-            this.providerIdentity = providerIdentity;
-            this.customData = customData;
-        }
-        
-    }
-    
-    @Override
-    boolean isNestedBlockRepeater() {
-        return true;
-    }
-
-    @Override
-    boolean isShownInStackTrace() {
-        return true;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
new file mode 100644
index 0000000..b178547
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDynamicTopLevelCall.java
@@ -0,0 +1,484 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core;
+
+import java.io.IOException;
+import java.io.Writer;
+import java.util.Collection;
+import java.util.LinkedHashMap;
+
+import org.apache.freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.ASTThreadInterruptionCheck;
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.CallPlace;
+import org.apache.freemarker.core.model.Constants;
+import org.apache.freemarker.core.model.TemplateCallableModel;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util.CommonSupplier;
+import org.apache.freemarker.core.util.StringToIndexMap;
+import org.apache.freemarker.core.util._ArrayAdapterList;
+import org.apache.freemarker.core.util._StringUtil;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+/**
+ * AST node: {@code <@exp ...>}.
+ * Executes a {@link TemplateCallableModel} that's embeddable directly into the static text (hence "top level"). At
+ * least in the default template language the value must be a {@link TemplateDirectiveModel}, though technically
+ * calling a {@link TemplateFunctionModel} is possible as well (hence it's not called "dynamic directive call").
+ * <p>
+ * The {@link TemplateCallableModel} object is obtained on runtime by evaluating an expression, and the parameter list
+ * is also validated (how many positional parameters are allowed, what named parameters are supported) then. Hence, the
+ * call is "dynamic".
+ */
+class ASTDynamicTopLevelCall extends ASTDirective implements CallPlace {
+
+    static final class NamedArgument {
+        private final String name;
+        private final ASTExpression value;
+
+        public NamedArgument(String name, ASTExpression value) {
+            this.name = name;
+            this.value = value;
+        }
+    }
+
+    private final ASTExpression callableValueExp;
+    private final ASTExpression[] positionalArgs;
+    private final NamedArgument[] namedArgs;
+    private final StringToIndexMap loopVarNames;
+    private final boolean allowCallingFunctions;
+
+    private CustomDataHolder customDataHolder;
+
+    /**
+     * @param allowCallingFunctions Some template languages may allow calling {@link TemplateFunctionModel}-s
+     *                              directly embedded into the static text, in which case this should be {@code true}.
+     */
+    ASTDynamicTopLevelCall(
+            ASTExpression callableValueExp, boolean allowCallingFunctions,
+            ASTExpression[] positionalArgs, NamedArgument[] namedArgs, StringToIndexMap loopVarNames,
+            TemplateElements children) {
+        this.callableValueExp = callableValueExp;
+        this.allowCallingFunctions = allowCallingFunctions;
+
+        if (positionalArgs != null && positionalArgs.length == 0
+                || namedArgs != null && namedArgs.length == 0
+                || loopVarNames != null && loopVarNames.size() == 0) {
+            throw new IllegalArgumentException("Use null instead of empty collections");
+        }
+        this.positionalArgs = positionalArgs;
+        this.namedArgs = namedArgs;
+        this.loopVarNames = loopVarNames;
+
+        setChildren(children);
+    }
+
+    @Override
+    ASTElement[] accept(Environment env) throws TemplateException, IOException {
+        TemplateCallableModel callableValue;
+        TemplateDirectiveModel directive;
+        TemplateFunctionModel function;
+        {
+            TemplateModel callableValueTM = callableValueExp._eval(env);
+            if (callableValueTM instanceof TemplateDirectiveModel) {
+                callableValue = (TemplateCallableModel) callableValueTM;
+                directive = (TemplateDirectiveModel) callableValueTM;
+                function = null;
+            } else if (callableValueTM instanceof TemplateFunctionModel) {
+                if (!allowCallingFunctions) {
+                    // TODO [FM3][CF] Better exception
+                    throw new NonUserDefinedDirectiveLikeException(
+                            "Calling functions is not allowed on the top level in this template language", env);
+                }
+                callableValue = (TemplateCallableModel) callableValueTM;
+                directive = null;
+                function = (TemplateFunctionModel) callableValue;
+            } else if (callableValueTM instanceof ASTDirMacro) {
+                // TODO [FM3][CF] Until macros were refactored to be TemplateDirectiveModel-s, we have this hack here.
+                ASTDirMacro macro = (ASTDirMacro) callableValueTM;
+                if (macro.isFunction()) {
+                    throw new _MiscTemplateException(env,
+                            "Routine ", new _DelayedJQuote(macro.getName()), " is a function, not a directive. "
+                            + "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ",
+                            "<@someDirective someParam=f() />", ".");
+                }
+
+                // We have to convert arguments to the legacy data structures... yet again, it's only a temporary hack.
+                LinkedHashMap<String, ASTExpression> macroNamedArgs;
+                if (namedArgs != null) {
+                    macroNamedArgs = new LinkedHashMap<>(namedArgs.length * 4 / 3);
+                    for (NamedArgument namedArg : namedArgs) {
+                        macroNamedArgs.put(namedArg.name, namedArg.value);
+                    }
+                } else {
+                    macroNamedArgs = null;
+                }
+                env.invoke(macro,
+                        macroNamedArgs,
+                        _ArrayAdapterList.adapt(positionalArgs),
+                        loopVarNames != null ? loopVarNames.getKeys() : null,
+                        getChildBuffer());
+                return null;
+            } else if (callableValueTM == null) {
+                throw InvalidReferenceException.getInstance(callableValueExp, env);
+            } else {
+                throw new NonUserDefinedDirectiveLikeException(callableValueExp, callableValueTM, env);
+            }
+        }
+
+        ArgumentArrayLayout argsLayout = callableValue.getArgumentArrayLayout();
+        int predefPosArgCnt = argsLayout.getPredefinedPositionalArgumentCount();
+        int posVarargsArgIdx = argsLayout.getPositionalVarargsArgumentIndex();
+
+        TemplateModel[] execArgs = new TemplateModel[argsLayout.getTotalLength()];
+
+        // Fill predefined positional args:
+        if (positionalArgs != null) {
+            int actualPredefPosArgCnt = Math.min(positionalArgs.length, predefPosArgCnt);
+            for (int argIdx = 0; argIdx < actualPredefPosArgCnt; argIdx++) {
+                execArgs[argIdx] = positionalArgs[argIdx].eval(env);
+            }
+        }
+
+        if (posVarargsArgIdx != -1) {
+            int posVarargCnt = positionalArgs != null ? positionalArgs.length - predefPosArgCnt : 0;
+            TemplateSequenceModel varargsSeq;
+            if (posVarargCnt <= 0) {
+                varargsSeq = Constants.EMPTY_SEQUENCE;
+            } else {
+                NativeSequence nativeSeq = new NativeSequence(posVarargCnt);
+                varargsSeq = nativeSeq;
+                for (int posVarargIdx = 0; posVarargIdx < posVarargCnt; posVarargIdx++) {
+                    nativeSeq.add(positionalArgs[predefPosArgCnt + posVarargIdx].eval(env));
+                }
+            }
+            execArgs[posVarargsArgIdx] = varargsSeq;
+        } else if (positionalArgs != null && positionalArgs.length > predefPosArgCnt) {
+            throw new _MiscTemplateException(this,
+                    "The target callable ",
+                    (predefPosArgCnt != 0
+                            ? new Object[] { "can only have ", predefPosArgCnt }
+                            : "can't have"
+                    ),
+                    " arguments passed by position, but the invocation has ",
+                    positionalArgs.length, " such arguments.");
+        }
+
+        int namedVarargsArgumentIndex = argsLayout.getNamedVarargsArgumentIndex();
+        NativeHashEx2 namedVarargsHash = null;
+        if (namedArgs != null) {
+            StringToIndexMap predefNamedArgsMap = argsLayout.getPredefinedNamedArgumentsMap();
+            for (NamedArgument namedArg : namedArgs) {
+                int argIdx = predefNamedArgsMap.get(namedArg.name);
+                if (argIdx != -1) {
+                    execArgs[argIdx] = namedArg.value.eval(env);
+                } else {
+                    if (namedVarargsHash == null) {
+                        if (namedVarargsArgumentIndex == -1) {
+                            Collection<String> validNames = predefNamedArgsMap.getKeys();
+                            throw new _MiscTemplateException(this,
+                                    validNames == null || validNames.isEmpty()
+                                    ? new Object[] {
+                                            "The target callable doesn't have any by-name-passed parameters (like ",
+                                            new _DelayedJQuote(namedArg.name), ")"
+                                    }
+                                    : new Object[] {
+                                            "The target callable has no by-name-passed parameter called ",
+                                            new _DelayedJQuote(namedArg.name), ". The supported parameter names are:\n",
+                                            new _DelayedJQuotedListing(validNames)
+                                    });
+                        }
+
+                        namedVarargsHash = new NativeHashEx2();
+                    }
+                    namedVarargsHash.put(namedArg.name, namedArg.value.eval(env));
+                }
+            }
+        }
+        if (namedVarargsArgumentIndex != -1) {
+            execArgs[namedVarargsArgumentIndex] = namedVarargsHash != null ? namedVarargsHash : Constants.EMPTY_HASH;
+        }
+
+        if (directive != null) {
+            directive.execute(execArgs, this, env.getOut(), env);
+        } else {
+            TemplateModel result = function.execute(execArgs, env, this);
+            if (result == null) {
+                throw new _MiscTemplateException(this, "Function has returned no value (or null)");
+            }
+            // TODO [FM3][CF]
+            throw new BugException("Top-level function call not yet implemented");
+        }
+
+        return null;
+    }
+
+    @Override
+    boolean isNestedBlockRepeater() {
+        return true;
+    }
+
+    @Override
+    boolean isShownInStackTrace() {
+        return true;
+    }
+
+    @Override
+    protected String dump(boolean canonical) {
+        StringBuilder sb = new StringBuilder();
+        if (canonical) sb.append('<');
+        sb.append('@');
+        MessageUtil.appendExpressionAsUntearable(sb, callableValueExp);
+        boolean nameIsInParen = sb.charAt(sb.length() - 1) == ')';
+        if (positionalArgs != null) {
+            for (int i = 0; i < positionalArgs.length; i++) {
+                ASTExpression argExp = (ASTExpression) positionalArgs[i];
+                if (i != 0) {
+                    sb.append(',');
+                }
+                sb.append(' ');
+                sb.append(argExp.getCanonicalForm());
+            }
+        }
+        if (namedArgs != null) {
+            for (NamedArgument namedArg : namedArgs) {
+                sb.append(' ');
+                sb.append(_StringUtil.toFTLTopLevelIdentifierReference(namedArg.name));
+                sb.append('=');
+                MessageUtil.appendExpressionAsUntearable(sb, namedArg.value);
+            }
+        }
+        if (loopVarNames != null) {
+            sb.append("; ");
+            boolean first = true;
+            for (String loopVarName : loopVarNames.getKeys()) {
+                if (!first) {
+                    sb.append(", ");
+                } else {
+                    first = false;
+                }
+                sb.append(_StringUtil.toFTLTopLevelIdentifierReference(loopVarName));
+            }
+        }
+        if (canonical) {
+            if (getChildCount() == 0) {
+                sb.append("/>");
+            } else {
+                sb.append('>');
+                sb.append(getChildrenCanonicalForm());
+                sb.append("</@");
+                if (!nameIsInParen
+                        && (callableValueExp instanceof ASTExpVariable
+                        || (callableValueExp instanceof ASTExpDot && ((ASTExpDot) callableValueExp).onlyHasIdentifiers()))) {
+                    sb.append(callableValueExp.getCanonicalForm());
+                }
+                sb.append('>');
+            }
+        }
+        return sb.toString();
+    }
+
+    @Override
+    String getASTNodeDescriptor() {
+        return "@";
+    }
+
+    @Override
+    int getParameterCount() {
+        return 1/*nameExp*/
+                + (positionalArgs != null ? positionalArgs.length : 0)
+                + (namedArgs != null ? namedArgs.length * 2 : 0)
+                + (loopVarNames != null ? loopVarNames.size() : 0);
+    }
+
+    @Override
+    Object getParameterValue(int idx) {
+        if (idx == 0) {
+            return callableValueExp;
+        } else {
+            int base = 1;
+            final int positionalArgsSize = positionalArgs != null ? positionalArgs.length : 0;
+            if (idx - base < positionalArgsSize) {
+                return positionalArgs[idx - base];
+            } else {
+                base += positionalArgsSize;
+                final int namedArgsSize = namedArgs != null ? namedArgs.length : 0;
+                if (idx - base < namedArgsSize * 2) {
+                    NamedArgument namedArg = namedArgs[(idx - base) / 2];
+                    return (idx - base) % 2 == 0 ? namedArg.name : namedArg.value;
+                } else {
+                    base += namedArgsSize * 2;
+                    final int bodyParameterNamesSize = loopVarNames != null ? loopVarNames.size() : 0;
+                    if (idx - base < bodyParameterNamesSize) {
+                        return loopVarNames.getKeys().get(idx - base);
+                    } else {
+                        throw new IndexOutOfBoundsException();
+                    }
+                }
+            }
+        }
+    }
+
+    @Override
+    ParameterRole getParameterRole(int idx) {
+        if (idx == 0) {
+            return ParameterRole.CALLEE;
+        } else {
+            int base = 1;
+            final int positionalArgsSize = positionalArgs != null ? positionalArgs.length : 0;
+            if (idx - base < positionalArgsSize) {
+                return ParameterRole.ARGUMENT_VALUE;
+            } else {
+                base += positionalArgsSize;
+                final int namedArgsSize = namedArgs != null ? namedArgs.length : 0;
+                if (idx - base < namedArgsSize * 2) {
+                    return (idx - base) % 2 == 0 ? ParameterRole.ARGUMENT_NAME : ParameterRole.ARGUMENT_VALUE;
+                } else {
+                    base += namedArgsSize * 2;
+                    final int bodyParameterNamesSize = loopVarNames != null ? loopVarNames.size() : 0;
+                    if (idx - base < bodyParameterNamesSize) {
+                        return ParameterRole.TARGET_LOOP_VARIABLE;
+                    } else {
+                        throw new IndexOutOfBoundsException();
+                    }
+                }
+            }
+        }
+    }
+
+    // -----------------------------------------------------------------------------------------------------------------
+    // CallPlace API:
+
+    @Override
+    public boolean hasNestedContent() {
+        int childCount = getChildCount();
+        return childCount != 0 && (childCount > 1 || !(getChild(0) instanceof ASTThreadInterruptionCheck));
+    }
+
+    @Override
+    public int getLoopVariableCount() {
+        return loopVarNames != null ? loopVarNames.size() : 0;
+    }
+
+    @Override
+    public void executeNestedContent(TemplateModel[] loopVarValues, Writer out, Environment env)
+            throws TemplateException, IOException {
+        if (loopVarNames != null) {
+            int loopVarNamesSize = loopVarNames.size();
+            int loopVarValuesSize = loopVarValues != null ? loopVarValues.length : 0;
+            if (loopVarValuesSize < loopVarNamesSize) {
+                throw new _MiscTemplateException(this,
+                        "The invocation declares more nested content parameters (",
+                        loopVarNamesSize, ": ", new _DelayedJQuotedListing(loopVarNames.getKeys()),
+                        ") than what the called object intends to pass (",
+                        loopVarValuesSize, "). Declare no more than ", loopVarValuesSize,
+                        " nested content parameters.");
+            }
+        }
+        env.visit(getChildBuffer(), loopVarNames, loopVarValues, out);
+    }
+
+    @Override
+    @SuppressFBWarnings(value={ "IS2_INCONSISTENT_SYNC", "DC_DOUBLECHECK" }, justification="Performance tricks")
+    public Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier)
+            throws CallPlaceCustomDataInitializationException {
+        // We are using double-checked locking, utilizing Java memory model "final" trick.
+        // Note that this.customDataHolder is NOT volatile.
+
+        CustomDataHolder customDataHolder = this.customDataHolder;  // Findbugs false alarm
+        if (customDataHolder == null) {  // Findbugs false alarm
+            synchronized (this) {
+                customDataHolder = this.customDataHolder;
+                if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) {
+                    customDataHolder = createNewCustomData(providerIdentity, supplier);
+                    this.customDataHolder = customDataHolder;
+                }
+            }
+        }
+
+        if (customDataHolder.providerIdentity != providerIdentity) {
+            synchronized (this) {
+                customDataHolder = this.customDataHolder;
+                if (customDataHolder == null || customDataHolder.providerIdentity != providerIdentity) {
+                    customDataHolder = createNewCustomData(providerIdentity, supplier);
+                    this.customDataHolder = customDataHolder;
+                }
+            }
+        }
+
+        return customDataHolder.customData;
+    }
+
+    private CustomDataHolder createNewCustomData(Object provierIdentity, CommonSupplier supplier)
+            throws CallPlaceCustomDataInitializationException {
+        CustomDataHolder customDataHolder;
+        Object customData;
+        try {
+            customData = supplier.get();
+        } catch (Exception e) {
+            throw new CallPlaceCustomDataInitializationException(
+                    "Failed to initialize custom data for provider identity "
+                            + _StringUtil.tryToString(provierIdentity) + " via factory "
+                            + _StringUtil.tryToString(supplier), e);
+        }
+        if (customData == null) {
+            throw new NullPointerException("CommonSupplier.get() has returned null");
+        }
+        customDataHolder = new CustomDataHolder(provierIdentity, customData);
+        return customDataHolder;
+    }
+
+    @Override
+    public boolean isNestedOutputCacheable() {
+        return isChildrenOutputCacheable();
+    }
+
+    @Override
+    public int getFirstTargetJavaParameterTypeIndex() {
+        // TODO [FM3]
+        return -1;
+    }
+
+    @Override
+    public Class<?> getTargetJavaParameterType(int argIndex) {
+        // TODO [FM3]
+        return null;
+    }
+
+    /**
+     * Used for implementing double check locking in implementing the
+     * {@link #getOrCreateCustomData(Object, CommonSupplier)}.
+     */
+    private static class CustomDataHolder {
+
+        private final Object providerIdentity;
+        private final Object customData;
+        public CustomDataHolder(Object providerIdentity, Object customData) {
+            this.providerIdentity = providerIdentity;
+            this.customData = customData;
+        }
+
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
index c658ba8..4101b29 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
@@ -150,8 +150,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable {
         putBI("isSequence", new BuiltInsForMultipleTypes.is_sequenceBI());
         putBI("isString", new BuiltInsForMultipleTypes.is_stringBI());
         putBI("isTime", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.TIME));
-        putBI("isTransform", new BuiltInsForMultipleTypes.is_transformBI());
-        
+
         putBI("isoUtc", new iso_utc_or_local_BI(
                 /* showOffset = */ null, _DateUtil.ACCURACY_SECONDS, /* useUTC = */ true));
         putBI("isoUtcFZ", new iso_utc_or_local_BI(

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
index 4566beb..467a2f4 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -38,7 +38,6 @@ import org.apache.freemarker.core.model.TemplateNodeModel;
 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.impl.SimpleDate;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl.SimpleScalar;
@@ -314,7 +313,7 @@ class BuiltInsForMultipleTypes {
             TemplateModel tm = target.eval(env);
             target.assertNonNull(tm, env);
             // WRONG: it also had to check ASTDirMacro.isFunction()
-            return (tm instanceof TemplateTransformModel || tm instanceof ASTDirMacro || tm instanceof TemplateDirectiveModel) ?
+            return (tm instanceof ASTDirMacro || tm instanceof TemplateDirectiveModel) ?
                 TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
         }
     }
@@ -427,16 +426,6 @@ class BuiltInsForMultipleTypes {
         }
     }
 
-    static class is_transformBI extends ASTExpBuiltIn {
-        @Override
-        TemplateModel _eval(Environment env) throws TemplateException {
-            TemplateModel tm = target.eval(env);
-            target.assertNonNull(tm, env);
-            return (tm instanceof TemplateTransformModel)  ?
-                TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
-        }
-    }
-
     static class namespaceBI extends ASTExpBuiltIn {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index 1e82923..4d15516 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -21,19 +21,19 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 import java.io.StringReader;
+import java.io.Writer;
 import java.util.List;
-import java.util.Map;
 
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
 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.TemplateScalarModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.TemplateTransformModel;
 import org.apache.freemarker.core.model.impl.BeanModel;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
@@ -133,7 +133,7 @@ class BuiltInsForStringsMisc {
         
         /**
          * Constructs a template on-the-fly and returns it embedded in a
-         * {@link TemplateTransformModel}.
+         * {@link TemplateDirectiveModel}.
          * 
          * <p>The built-in has two arguments:
          * the arguments passed to the method. It can receive at
@@ -142,7 +142,7 @@ class BuiltInsForStringsMisc {
          * is built from it. The second (optional) is used to give the generated
          * template a name.
          * 
-         * @return a {@link TemplateTransformModel} that when executed inside
+         * @return a {@link TemplateDirectiveModel} that when executed inside
          * a <tt>&lt;transform></tt> block will process the generated template
          * just as if it had been <tt>&lt;transform></tt>-ed at that point.
          */
@@ -198,9 +198,9 @@ class BuiltInsForStringsMisc {
             }
 
             @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 {
-                // TODO [FM3] Disallow params, loop vars, and nested content
+                // TODO [FM3] Disallow loop vars, and nested content
                 try {
                     boolean lastFIRE = env.setFastInvalidReferenceExceptions(false);
                     try {
@@ -215,9 +215,12 @@ class BuiltInsForStringsMisc {
                             new _DelayedGetMessage(e),
                             MessageUtil.EMBEDDED_MESSAGE_END);
                 }
-                if (body != null) {
-                    body.render(env.getOut());
-                }
+                callPlace.executeNestedContent(null, out, env);
+            }
+
+            @Override
+            public ArgumentArrayLayout getArgumentArrayLayout() {
+                return ArgumentArrayLayout.PARAMETERLESS;
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
index 810ccba..0033afd 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/CallPlaceCustomDataInitializationException.java
@@ -19,10 +19,11 @@
 
 package org.apache.freemarker.core;
 
+import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.util.CommonSupplier;
 
 /**
- * Thrown by {@link DirectiveCallPlace#getOrCreateCustomData(Object, CommonSupplier)}
+ * Thrown by {@link CallPlace#getOrCreateCustomData(Object, CommonSupplier)}
  */
 public class CallPlaceCustomDataInitializationException extends Exception {
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
deleted file mode 100644
index d6392ac..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/DirectiveCallPlace.java
+++ /dev/null
@@ -1,135 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core;
-
-import java.util.IdentityHashMap;
-
-import org.apache.freemarker.core.model.TemplateDirectiveModel;
-import org.apache.freemarker.core.model.TemplateTransformModel;
-import org.apache.freemarker.core.util.CommonSupplier;
-
-/**
- * Gives information about the place where a directive is called from, also lets you attach a custom data object to that
- * place. Each directive call in a template has its own {@link DirectiveCallPlace} object (even when they call the same
- * directive with the same parameters). The life cycle of the {@link DirectiveCallPlace} object is bound to the
- * {@link Template} object that contains the directive call. Hence, the {@link DirectiveCallPlace} object and the custom
- * data you put into it is cached together with the {@link Template} (and templates are normally cached - see
- * {@link Configuration#getTemplate(String)}). The custom data is normally initialized on demand, that is, when the
- * directive call is first executed, via {@link #getOrCreateCustomData(Object, CommonSupplier)}.
- * 
- * <p>
- * Currently this method doesn't give you access to the {@link Template} object, because it's probable that future
- * versions of FreeMarker will be able to use the same parsed representation of a "file" for multiple {@link Template}
- * objects. Then the call place will be bound to the parsed representation, not to the {@link Template} objects that are
- * based on it.
- * 
- * <p>
- * <b>Don't implement this interface yourself</b>, as new methods can be added to it any time! It's only meant to be
- * implemented by the FreeMarker core.
- * 
- * <p>
- * This interface is currently only used for custom directive calls (that is, a {@code <@...>} that calls a
- * {@link TemplateDirectiveModel}, {@link TemplateTransformModel}, or a macro).
- * 
- * @see Environment#getCurrentDirectiveCallPlace()
- */
-public interface DirectiveCallPlace {
-
-    /**
-     * The 1-based column number of the first character of the directive call in the template source code, or -1 if it's
-     * not known.
-     */
-    int getBeginColumn();
-
-    /**
-     * The 1-based line number of the first character of the directive call in the template source code, or -1 if it's
-     * not known.
-     */
-    int getBeginLine();
-
-    /**
-     * The 1-based column number of the last character of the directive call in the template source code, or -1 if it's
-     * not known. If the directive has an end-tag ({@code </@...>}), then it points to the last character of that.
-     */
-    int getEndColumn();
-
-    /**
-     * The 1-based line number of the last character of the directive call in the template source code, or -1 if it's
-     * not known. If the directive has an end-tag ({@code </@...>}), then it points to the last character of that.
-     */
-    int getEndLine();
-
-    /**
-     * 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.
-     * 
-     * <p>
-     * This method will block other calls while the {@code supplier} is executing, thus, the object will be
-     * <em>usually</em> created only once, even if multiple threads request the value when it's still {@code null}. It
-     * doesn't stand though when {@code providerIdentity} mismatches occur (see later). Furthermore, then it's also
-     * possible that multiple objects created by the same {@link CommonSupplier} will be in use on the same time, because
-     * of directive executions already running in parallel, and because of memory synchronization delays (hardware
-     * dependent) between the threads.
-     * 
-     * @param providerIdentity
-     *            This is usually the class of the {@link TemplateDirectiveModel} that creates (and uses) the custom
-     *            data, or if you are using your own class for the custom data object (as opposed to a class from some
-     *            more generic API), then that class. This is needed as the same call place might calls different
-     *            directives depending on runtime conditions, and so it must be ensured that these directives won't
-     *            accidentally read each other's custom data, ending up with class cast exceptions or worse. In the
-     *            current implementation, if there's a {@code providerIdentity} mismatch (means, the
-     *            {@code providerIdentity} object used when the custom data was last set isn't the exactly same object
-     *            as the one provided with the parameter now), the previous custom data will be just ignored as if it
-     *            was {@code null}. So if multiple directives that use the custom data feature use the same call place,
-     *            the caching of the custom data can be inefficient, as they will keep overwriting each other's custom
-     *            data. (In a more generic implementation the {@code providerIdentity} would be a key in a
-     *            {@link IdentityHashMap}, but then this feature would be slower, while {@code providerIdentity}
-     *            mismatches aren't occurring in most applications.)
-     * @param supplier
-     *            Called when the custom data wasn't yet set, to invoke its initial value. If this parameter is
-     *            {@code null} and the custom data wasn't set yet, then {@code null} will be returned. The returned
-     *            value of {@link CommonSupplier#get()} can be any kind of object, but can't be {@code null}.
-     * 
-     * @return The current custom data object, or possibly {@code null} if there was no {@link CommonSupplier} provided.
-     * 
-     * @throws CallPlaceCustomDataInitializationException
-     *             If the {@link CommonSupplier} had to be invoked but failed.
-     */
-    Object getOrCreateCustomData(Object providerIdentity, CommonSupplier supplier)
-            throws CallPlaceCustomDataInitializationException;
-
-    /**
-     * Tells if the nested content (the body) can be safely cached, as it only depends on the template content (not on
-     * variable values and such) and has no side-effects (other than writing to the output). Examples of cases that give
-     * {@code false}: {@code <@foo>Name: } <tt>${name}</tt>{@code</@foo>},
-     * {@code <@foo>Name: <#if showIt>Joe</#if></@foo>}. Examples of cases that give {@code true}:
-     * {@code <@foo>Name: Joe</@foo>}, {@code <@foo />}. Note that we get {@code true} for no nested content, because
-     * that's equivalent with 0-length nested content in FTL.
-     * 
-     * <p>
-     * This method returns a pessimistic result. For example, if it sees a custom directive call, it can't know what it
-     * does, so it will assume that it's not cacheable.
-     */
-    boolean isNestedOutputCacheable();
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
index 3be9fb5..ad6538d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
@@ -46,7 +46,6 @@ import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 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.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
@@ -57,14 +56,11 @@ import org.apache.freemarker.core.model.TemplateNodeModel;
 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.TransformControl;
 import org.apache.freemarker.core.model.impl.SimpleHash;
 import org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
 import org.apache.freemarker.core.templateresolver.TemplateResolver;
 import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat;
 import org.apache.freemarker.core.util.StringToIndexMap;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
 import org.apache.freemarker.core.util._DateUtil;
 import org.apache.freemarker.core.util._DateUtil.DateToISO8601CalendarFactory;
 import org.apache.freemarker.core.util._NullWriter;
@@ -237,8 +233,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     /**
      * Returns the {@link Template} that we are "lexically" inside at the moment. This template will change when
      * entering an {@code #include} or calling a macro or function in another template, or returning to yet another
-     * template with {@code #nested}. As such, it's useful in {@link TemplateDirectiveModel} to find out if from where
-     * the directive was called from.
+     * template with {@code #nested}.
      * 
      * @see #getMainTemplate()
      * @see #getCurrentNamespace()
@@ -258,24 +253,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     /**
-     * Gets the currently executing <em>custom</em> directive's call place information, or {@code null} if there's no
-     * executing custom directive. This currently only works for calls made from templates with the {@code <@...>}
-     * syntax. This should only be called from the {@link TemplateDirectiveModel} that was invoked with {@code <@...>},
-     * otherwise its return value is not defined by this API (it's usually {@code null}).
-     */
-    @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False alarm")
-    public DirectiveCallPlace getCurrentDirectiveCallPlace() {
-        int ln = instructionStackSize;
-        if (ln == 0) return null;
-        ASTElement te = instructionStack[ln - 1];
-        if (te instanceof ASTDirUserDefined) return (ASTDirUserDefined) te;
-        if (te instanceof ASTDirMacro && ln > 1 && instructionStack[ln - 2] instanceof ASTDirUserDefined) {
-            return (ASTDirUserDefined) instructionStack[ln - 2];
-        }
-        return null;
-    }
-
-    /**
      * Deletes cached values that meant to be valid only during a single template execution.
      */
     private void clearCachedValues() {
@@ -450,51 +427,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
 
     private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
 
-    void visit(final ASTElement element,
-            TemplateDirectiveModel directiveModel, Map args,
-            final List bodyParameterNames) throws TemplateException, IOException {
-        visit(new ASTElement[] { element }, directiveModel, args, bodyParameterNames);
-    }
-    
-    void visit(final ASTElement[] childBuffer,
-            TemplateDirectiveModel directiveModel, Map args,
-            final List bodyParameterNames) throws TemplateException, IOException {
-        TemplateDirectiveBody nested;
-        if (childBuffer == null) {
-            nested = null;
-        } else {
-            nested = new NestedElementTemplateDirectiveBody(childBuffer);
-        }
-        final TemplateModel[] outArgs;
-        if (bodyParameterNames == null || bodyParameterNames.isEmpty()) {
-            outArgs = NO_OUT_ARGS;
-        } else {
-            outArgs = new TemplateModel[bodyParameterNames.size()];
-        }
-        if (outArgs.length > 0) {
-            pushLocalContext(new LocalContext() {
-
-                @Override
-                public TemplateModel getLocalVariable(String name) {
-                    int index = bodyParameterNames.indexOf(name);
-                    return index != -1 ? outArgs[index] : null;
-                }
-
-                @Override
-                public Collection<String> getLocalVariableNames() {
-                    return bodyParameterNames;
-                }
-            });
-        }
-        try {
-            directiveModel.execute(this, args, outArgs, nested);
-        } finally {
-            if (outArgs.length > 0) {
-                popLocalContext();
-            }
-        }
-    }
-
     void visit(
             ASTElement[] childBuffer,
             final StringToIndexMap loopVarNames, final TemplateModel[] loopVarValues,
@@ -535,62 +467,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     /**
-     * "Visit" the template element, passing the output through a TemplateTransformModel
-     * 
-     * @param elementBuffer
-     *            the element to visit through a transform; might contains trailing {@code null}-s
-     * @param transform
-     *            the transform to pass the element output through
-     * @param args
-     *            optional arguments fed to the transform
-     */
-    void visitAndTransform(ASTElement[] elementBuffer,
-            TemplateTransformModel transform,
-            Map args)
-                    throws TemplateException, IOException {
-        try {
-            Writer tw = transform.getWriter(out, args);
-            if (tw == null) tw = EMPTY_BODY_WRITER;
-            TransformControl tc = tw instanceof TransformControl
-                    ? (TransformControl) tw
-                    : null;
-
-            Writer prevOut = out;
-            out = tw;
-            try {
-                if (tc == null || tc.onStart() != TransformControl.SKIP_BODY) {
-                    do {
-                        visit(elementBuffer);
-                    } while (tc != null && tc.afterBody() == TransformControl.REPEAT_EVALUATION);
-                }
-            } catch (Throwable t) {
-                try {
-                    if (tc != null) {
-                        tc.onError(t);
-                    } else {
-                        throw t;
-                    }
-                } catch (TemplateException e) {
-                    throw e;
-                } catch (IOException e) {
-                    throw e;
-                } catch (RuntimeException e) {
-                    throw e;
-                } catch (Error e) {
-                    throw e;
-                } catch (Throwable e) {
-                    throw new UndeclaredThrowableException(e);
-                }
-            } finally {
-                out = prevOut;
-                tw.close();
-            }
-        } catch (TemplateException te) {
-            handleTemplateException(te);
-        }
-    }
-
-    /**
      * Visit a block using buffering/recovery
      */
      void visitAttemptRecover(
@@ -704,11 +580,12 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             nodeNamespaces = namespaces;
         }
         try {
-            TemplateModel macroOrTransform = getNodeProcessor(node);
-            if (macroOrTransform instanceof ASTDirMacro) {
-                invoke((ASTDirMacro) macroOrTransform, null, null, null, null);
-            } else if (macroOrTransform instanceof TemplateTransformModel) {
-                visitAndTransform(null, (TemplateTransformModel) macroOrTransform, null);
+            TemplateModel macroOrDirective = getNodeProcessor(node);
+            if (macroOrDirective instanceof ASTDirMacro) {
+                invoke((ASTDirMacro) macroOrDirective, null, null, null, null);
+            } else if (macroOrDirective instanceof TemplateDirectiveModel) {
+                ((TemplateDirectiveModel) macroOrDirective).execute(
+                        null, null /* TODO [FM3][CF] */, out, this);
             } else {
                 String nodeType = node.getNodeType();
                 if (nodeType != null) {
@@ -760,11 +637,12 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
     }
 
     void fallback() throws TemplateException, IOException {
-        TemplateModel macroOrTransform = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
-        if (macroOrTransform instanceof ASTDirMacro) {
-            invoke((ASTDirMacro) macroOrTransform, null, null, null, null);
-        } else if (macroOrTransform instanceof TemplateTransformModel) {
-            visitAndTransform(null, (TemplateTransformModel) macroOrTransform, null);
+        TemplateModel macroOrDirective = getNodeProcessor(currentNodeName, currentNodeNS, nodeNamespaceIndex);
+        if (macroOrDirective instanceof ASTDirMacro) {
+            invoke((ASTDirMacro) macroOrDirective, null, null, null, null);
+        } else if (macroOrDirective instanceof TemplateDirectiveModel) {
+            ((TemplateDirectiveModel) macroOrDirective).execute(
+                    null, null /* TODO [FM3][CF] */, out, this);
         }
     }
 
@@ -2076,20 +1954,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return isoBuiltInCalendarFactory;
     }
 
-    TemplateTransformModel getTransform(ASTExpression exp) throws TemplateException {
-        TemplateTransformModel ttm = null;
-        TemplateModel tm = exp.eval(this);
-        if (tm instanceof TemplateTransformModel) {
-            ttm = (TemplateTransformModel) tm;
-        } else if (exp instanceof ASTExpVariable) {
-            tm = configuration.getWrappedSharedVariable(exp.toString());
-            if (tm instanceof TemplateTransformModel) {
-                ttm = (TemplateTransformModel) tm;
-            }
-        }
-        return ttm;
-    }
-
     /**
      * Returns the loop or macro local variable corresponding to this variable name. Possibly null. (Note that the
      * misnomer is kept for backward compatibility: loop variables are not local variables according to our
@@ -2609,7 +2473,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         TemplateModel result = null;
         if (nsURI == null) {
             result = ns.get(localName);
-            if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateTransformModel)) {
+            if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
                 result = null;
             }
         } else {
@@ -2622,25 +2486,25 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             }
             if (prefix.length() > 0) {
                 result = ns.get(prefix + ":" + localName);
-                if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateTransformModel)) {
+                if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
                     result = null;
                 }
             } else {
                 if (nsURI.length() == 0) {
                     result = ns.get(Template.NO_NS_PREFIX + ":" + localName);
-                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateTransformModel)) {
+                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
                         result = null;
                     }
                 }
                 if (nsURI.equals(template.getDefaultNS())) {
                     result = ns.get(Template.DEFAULT_NAMESPACE_PREFIX + ":" + localName);
-                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateTransformModel)) {
+                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
                         result = null;
                     }
                 }
                 if (result == null) {
                     result = ns.get(localName);
-                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateTransformModel)) {
+                    if (!(result instanceof ASTDirMacro) && !(result instanceof TemplateDirectiveModel)) {
                         result = null;
                     }
                 }
@@ -2942,31 +2806,6 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return getConfiguration().getObjectWrapper();
     }
 
-    final class NestedElementTemplateDirectiveBody implements TemplateDirectiveBody {
-
-        private final ASTElement[] childBuffer;
-
-        private NestedElementTemplateDirectiveBody(ASTElement[] childBuffer) {
-            this.childBuffer = childBuffer;
-        }
-
-        @Override
-        public void render(Writer newOut) throws TemplateException, IOException {
-            Writer prevOut = out;
-            out = newOut;
-            try {
-                visit(childBuffer);
-            } finally {
-                out = prevOut;
-            }
-        }
-        
-        ASTElement[] getChildrenBuffer() {
-            return childBuffer;
-        }
-
-    }
-
     public class Namespace extends SimpleHash {
 
         private Template template;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java
index cca60ad..a6e6be5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NestedContentNotSupportedException.java
@@ -19,33 +19,23 @@
 
 package org.apache.freemarker.core;
 
-import org.apache.freemarker.core.Environment.NestedElementTemplateDirectiveBody;
-import org.apache.freemarker.core.ThreadInterruptionSupportTemplatePostProcessor.ASTThreadInterruptionCheck;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
+import org.apache.freemarker.core.model.CallPlace;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.util._StringUtil;
 
 /**
- * Used in custom {@link org.apache.freemarker.core.model.TemplateDirectiveModel}-s to check if the directive invocation
- * has no body. This is more intelligent than a {@code null} check; for example, when the body
- * only contains a thread interruption check node, it treats it as valid.
+ * Used in custom {@link TemplateDirectiveModel}-s to check if the directive invocation has no body. This is more
+ * intelligent than a {@code null} check; for example, when the body only contains a thread interruption check node, it
+ * treats it as valid.
  */
 public class NestedContentNotSupportedException extends TemplateException {
 
-    public static void check(TemplateDirectiveBody body) throws NestedContentNotSupportedException {
-        if (body == null) {
-            return;
+    public static void check(CallPlace body) throws NestedContentNotSupportedException {
+        if (body.hasNestedContent()) {
+            throw new NestedContentNotSupportedException(Environment.getCurrentEnvironment());
         }
-        if (body instanceof NestedElementTemplateDirectiveBody) {
-            ASTElement[] tes = ((NestedElementTemplateDirectiveBody) body).getChildrenBuffer();
-            if (tes == null || tes.length == 0
-                    || tes[0] instanceof ASTThreadInterruptionCheck && (tes.length == 1 || tes[1] == null)) {
-                return;
-            }
-        }
-        throw new NestedContentNotSupportedException(Environment.getCurrentEnvironment());
     }
-    
-    
+
     private NestedContentNotSupportedException(Environment env) {
         this(null, null, env);
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
index 1daae3e..547d688 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NonUserDefinedDirectiveLikeException.java
@@ -21,17 +21,16 @@ package org.apache.freemarker.core;
 
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateTransformModel;
 
 // TODO [FM3][CF] Review and rename this when TDM2 and TFM are in place
 /**
- * Indicates that a {@link TemplateDirectiveModel} or {@link TemplateTransformModel} or {@link ASTDirMacro} value was
+ * Indicates that a {@link TemplateDirectiveModel} or {@link ASTDirMacro} value was
  * expected, but the value had a different type.
  */
 class NonUserDefinedDirectiveLikeException extends UnexpectedTypeException {
 
     private static final Class[] EXPECTED_TYPES = new Class[] {
-        TemplateDirectiveModel.class, TemplateTransformModel.class, ASTDirMacro.class };
+        TemplateDirectiveModel.class, ASTDirMacro.class };
     
     public NonUserDefinedDirectiveLikeException(Environment env) {
         super(env, "Expecting user-defined directive, transform or macro value here");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java
index 6f52d2f..2b87cfe 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ParseException.java
@@ -424,7 +424,7 @@ public class ParseException extends IOException implements FMParserConstants {
                         case CLOSE_PAREN:
                             endNames.add("\"(\"");
                             break;
-                        case UNIFIED_CALL_END:
+                        case DYNAMIC_TOP_LEVEL_CALL_END:
                             endNames.add("@...");
                             break;
                     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
index 4ee9f77..8313d53 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/TemplateCallableModelUtils.java
@@ -5,8 +5,6 @@ import org.apache.freemarker.core.model.TemplateNumberModel;
 
 public class TemplateCallableModelUtils {
 
-    public static final TemplateModel[] EMPTY_TEMPLATE_MODEL_ARRAY = new TemplateModel[0];
-
     // TODO [FM3][CF] Add this to the other exception classes too
     public static TemplateNumberModel castArgumentToNumber(TemplateModel[] args, int argIndex, boolean allowNull,
             Environment env) throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
index 05a786e..687675c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
@@ -21,8 +21,8 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 
+import org.apache.freemarker.core.model.CallPlace;
 import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateDirectiveBody;
 
 /**
  * Not yet public; subject to change.
@@ -32,10 +32,10 @@ import org.apache.freemarker.core.model.TemplateDirectiveBody;
  * <ul>
  * <li>{@link TemplateDateModel}-s that care to explicitly check if their nested content is {@code null} might start to
  *   complain that you have specified a body despite that the directive doesn't support that. Directives should use
- *   {@link NestedContentNotSupportedException#check(TemplateDirectiveBody)} instead of a simple
+ *   {@link NestedContentNotSupportedException#check(CallPlace)} instead of a simple
  *   {@code null}-check to avoid this problem.</li>
  * <li>
- *   Software that uses {@link DirectiveCallPlace#isNestedOutputCacheable()} will always get {@code false}, because
+ *   Software that uses {@link CallPlace#isNestedOutputCacheable()} will always get {@code false}, because
  *   interruption checks ({@link ASTThreadInterruptionCheck} elements) are, obviously, not cacheable. This should only
  *   impact the performance.
  * <li>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java
index b5e0a58..0158bd9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/DebugModel.java
@@ -47,7 +47,7 @@ public interface DebugModel extends Remote {
     public static final int TYPE_HASH_EX       =  128;
     public static final int TYPE_METHOD        =  256;
     public static final int TYPE_METHOD_EX     =  512;
-    public static final int TYPE_TRANSFORM     = 1024;
+    public static final int TYPE_DIRECTIVE     = 1024;
     public static final int TYPE_ENVIRONMENT   = 2048;
     public static final int TYPE_TEMPLATE      = 4096;
     public static final int TYPE_CONFIGURATION = 8192;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
index bb11db3..e8e8cf1 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebugModelImpl.java
@@ -28,6 +28,7 @@ import java.util.List;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateMethodModel;
@@ -38,7 +39,6 @@ import org.apache.freemarker.core.model.TemplateModelIterator;
 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;
 
 /**
  */
@@ -158,7 +158,7 @@ class RmiDebugModelImpl extends UnicastRemoteObject implements DebugModel {
         else if (model instanceof TemplateHashModel) type += TYPE_HASH;
         if (model instanceof TemplateMethodModelEx) type += TYPE_METHOD_EX;
         else if (model instanceof TemplateMethodModel) type += TYPE_METHOD;
-        if (model instanceof TemplateTransformModel) type += TYPE_TRANSFORM;
+        if (model instanceof TemplateDirectiveModel) type += TYPE_DIRECTIVE;
         return type;
     }
 }


Mime
View raw message