http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java new file mode 100644 index 0000000..2a34703 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/ArgumentArrayLayout.java @@ -0,0 +1,199 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.core.model; + +import org.apache.freemarker.core.util.StringToIndexMap; + +/** + * {@link TemplateCallableModel} subinterfaces define a method called {@code execute}, which has an argument array + * parameter, whose layout this class describes. Each parameter has a constant index in this array, which is the same + * for all invocations of the same {@link TemplateCallableModel} object (regardless if there are omitted optional + * parameters). Thus, the argument values can always be accessed at these constant indexes; no runtime name lookup is + * needed inside the {@code execute} method of the {@link TemplateCallableModel} implementation. The + * {@link ArgumentArrayLayout} object is usually stored a static final field of the {@link TemplateCallableModel} + * implementation class. + * <p> + * The layout of the array is as follows: + * <ol> + * <li> + * {@link #getPredefinedPositionalArgumentCount()} elements for the predefined positional parameters. Index 0 + * corresponds to the 1st positional parameter. For omitted parameters the corresponding array element is {@code + * null}. + * <li> + * {@link #getPredefinedNamedArgumentsMap()}{@code .size()} elements for the predefined named arguments. These are at + * the indexes returned by {@link #getPredefinedNamedArgumentsMap()}{@code .get(String name)}. For omitted arguments + * the corresponding array element is {@code null}. + * <li> + * If there's a positional varargs argument, then one element for the positional varargs parameter, at + * index {@link #getPositionalVarargsArgumentIndex()}. + * <li> + * If there's a named varargs argument, then one element for the positional varargs parameter, at + * index {@link #getNamedVarargsArgumentIndex()}. + * </ol> + * <p> + * The length of the array is {@link #getTotalLength()}}, or more, in which case the extra elements should be + * ignored. + * <p> + * Instances of this class are immutable, thread-safe objects. + */ +public final class ArgumentArrayLayout { + private final int predefinedPositionalArgumentCount; + private final StringToIndexMap predefinedNamedArgumentsMap; + + private final int positionalVarargsArgumentIndex; + private final int namedVarargsArgumentIndex; + private final int arrayLength; + + /** Constant to be used when the {@link TemplateCallableModel} has no parameters. */ + public static final ArgumentArrayLayout PARAMETERLESS = new ArgumentArrayLayout( + 0, false, + null, false); + + /** Constant to be used when the {@link TemplateCallableModel} has 1 positional parameter, and no others. */ + public static final ArgumentArrayLayout SINGLE_POSITIONAL_PARAMETER = new ArgumentArrayLayout( + 1, false, + null, false); + + /** + * Creates a new instance, or returns some of the equivalent static constants (such as {@link #PARAMETERLESS} or + * {@link #SINGLE_POSITIONAL_PARAMETER}). + * + * @param predefinedPositionalArgumentCount + * The highest allowed number of positional arguments, not counting the positional varargs argument. The + * actual positional argument count can be less than this if there are optional positional argument. When + * calling the {@code execute} method of the {@link TemplateCallableModel}, this many items will be reserved + * for the positional arguments in the argument array (not counting the item for the positional varargs + * argument, if there's one). Positional arguments above this count will go to the varargs argument (if + * there's one, otherwise it's an error). + * @param hasPositionalVarargsArgument + * Specifies if there's a varargs argument into which positional arguments that aren't predefined are + * collected + * @param predefinedNamedArgumentsMap + * The valid names for named arguments (not counting named varargs arguments), and their indexes in the + * argument array passed to the {@code execute} method of the {@link TemplateCallableModel}. Can be {@code + * null}, which is equivalent to {@link StringToIndexMap#EMPTY}. Indexes must fall into the range starting + * with {@code predefinedPositionalArgumentCount} (inclusive), and ending with {@code + * predefinedPositionalArgumentCount}{@code + predefinedNamedArgumentsMap.size()} (exclusive). If not, an + * {@link IllegalArgumentException} will be thrown. (As {@link ArgumentArrayLayout}-s are normally created + * during class initialization, such an exception will later cause {@link NoClassDefFoundError} "Could not + * initialize" exceptions every time you try to access the class, which are not very informative. Only for + * the first access of the class will you get an {@link ExceptionInInitializerError} with the {@link + * IllegalArgumentException} as its cause exception, which contains the error details.) + * @param hasNamedVarargsArgument + * Specifies if there's a varargs argument into which named arguments that aren't predefined are collected + * + * @throws IllegalArgumentException + * If the {@code predefinedNamedArgumentsMap} contains indexes that are out of range. See the documentation + * of that parameter for more. + */ + public static ArgumentArrayLayout create( + int predefinedPositionalArgumentCount, boolean hasPositionalVarargsArgument, + StringToIndexMap predefinedNamedArgumentsMap, boolean hasNamedVarargsArgument) { + if ((predefinedNamedArgumentsMap == null || predefinedNamedArgumentsMap == StringToIndexMap.EMPTY) + && !hasPositionalVarargsArgument && !hasNamedVarargsArgument) { + if (predefinedPositionalArgumentCount == 0) { + return PARAMETERLESS; + } + if (predefinedPositionalArgumentCount == 1) { + return SINGLE_POSITIONAL_PARAMETER; + } + } + return new ArgumentArrayLayout( + predefinedPositionalArgumentCount, hasPositionalVarargsArgument, + predefinedNamedArgumentsMap, hasNamedVarargsArgument); + } + + private ArgumentArrayLayout(int predefinedPositionalArgumentCount, boolean hasPositionalVarargsArgument, + StringToIndexMap predefinedNamedArgumentsMap, boolean hasNamedVarargsArgument) { + if (predefinedNamedArgumentsMap == null) { + predefinedNamedArgumentsMap = StringToIndexMap.EMPTY; + } + + this.predefinedPositionalArgumentCount = predefinedPositionalArgumentCount; + this.predefinedNamedArgumentsMap = predefinedNamedArgumentsMap; + + int arrayLength = predefinedPositionalArgumentCount + predefinedNamedArgumentsMap.size(); + if (hasPositionalVarargsArgument) { + positionalVarargsArgumentIndex = arrayLength; + arrayLength++; + } else { + positionalVarargsArgumentIndex = -1; + } + if (hasNamedVarargsArgument) { + namedVarargsArgumentIndex = arrayLength; + arrayLength++; + } else { + namedVarargsArgumentIndex = -1; + } + this.arrayLength = arrayLength; + + predefinedNamedArgumentsMap.checkIndexRange(predefinedPositionalArgumentCount); + } + + /** + * See the related parameter of {@link ArgumentArrayLayout#create(int, boolean, StringToIndexMap, boolean)}. + */ + public int getPredefinedPositionalArgumentCount() { + return predefinedPositionalArgumentCount; + } + + /** + * See the related parameter of {@link ArgumentArrayLayout#create(int, boolean, StringToIndexMap, boolean)}. + */ + public StringToIndexMap getPredefinedNamedArgumentsMap() { + return predefinedNamedArgumentsMap; + } + + /** + * Returns the index of the varargs argument into which positional arguments that aren't predefined are collected, + * or -1 if there's no such varargs argument. The value of the positional varargs argument is a {@link + * TemplateSequenceModel} that collects all positional arguments whose index would be greater than or equal to + * {@link #getPredefinedPositionalArgumentCount()}. + * + * @return -1 if there's no such named argument + */ + public int getPositionalVarargsArgumentIndex() { + return positionalVarargsArgumentIndex; + } + + /** + * Returns the index of the varargs argument into which named arguments that aren't predefined are collected, or -1 + * if there's no such varargs argument. The value of the named varargs argument is a {@link TemplateHashModelEx2} + * with string keys that collects all the named arguments that aren't present in the {@link + * #getPredefinedNamedArgumentsMap()}. The iteration order of this hash follows the order in which the arguments + * were specified on the call site (in the template, typically). + * + * @return -1 if there's no such named argument + */ + public int getNamedVarargsArgumentIndex() { + return namedVarargsArgumentIndex; + } + + /** + * Returns the required (minimum) length of the {@code args} array that's passed to the {@code execute} method. As + * there's an index reserved for each predefined parameters, this length always includes the space reserved for + * optional parameters as well; it's not why it's said to be a minimum length. It's a minimum length because a + * longer array might be reused for better performance (but {@code execute} should never read those excess + * elements). + */ + public int getTotalLength() { + return arrayLength; + } +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java index 6518013..7761eeb 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/CallPlace.java @@ -24,10 +24,8 @@ import java.io.Writer; import java.util.IdentityHashMap; import org.apache.freemarker.core.CallPlaceCustomDataInitializationException; -import org.apache.freemarker.core.DirectiveCallPlace; import org.apache.freemarker.core.Environment; import org.apache.freemarker.core.Template; -import org.apache.freemarker.core.TemplateCallableModelUtils; import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core.util.CommonSupplier; @@ -49,19 +47,29 @@ public interface CallPlace { boolean hasNestedContent(); /** - * The number of loop variables specified for this call. + * The number of loop variables specified for this call. Note that users are generally allowed to omit loop + * variables when calling a directive. So this can be useful to avoid calculating the values of loop variables that + * the caller will not receive anyway. While it's an error if the user specifies too many loop variables, the + * implementor of the {@link TemplateDirectiveModel} shouldn't check for that condition, as far as they will call + * {@link #executeNestedContent(TemplateModel[], Writer, Environment)}, which will the should throw a {@link + * TemplateException} with a descriptive error message then. */ int getLoopVariableCount(); /** * Executed the nested content; it there's none, it just does nothing. * - * @param loopVariableValues - * The loop variables to pass to the nested content; not {@code null} (use {@link - * TemplateCallableModelUtils#EMPTY_TEMPLATE_MODEL_ARRAY}. Its length must be equal to - * {@link #getLoopVariableCount()}. + * @param loopVarValues + * The loop variable values to pass to the nested content, or {@code null} if there's none. It's not a + * problem if this array is longer than the number of loop variables than the caller of the directive has + * declared (as in {@code <@foo bar; i, j />} there are 2 loop variables declared); the directive + * caller simply won't receive the excess variables. If the caller declares more loop variables than the + * length of this array though, then a {@link TemplateException} will thrown by FreeMarker with a + * descriptive error message. Thus, the caller of this method need not be concerned about the + * number of loop variables declared by the caller (unless to avoid calculating loop variable values + * unnecessarily, in which case use {@link #getLoopVariableCount()}). */ - void executeNestedContent(TemplateModel[] loopVariableValues, Writer out, Environment env) + void executeNestedContent(TemplateModel[] loopVarValues, Writer out, Environment env) throws TemplateException, IOException; // ------------------------------------------------------------------------------------------------------------- @@ -102,41 +110,38 @@ public interface CallPlace { /** * Returns the custom data, or if that's {@code null}, then it creates and stores it in an atomic operation then * returns it. This method is thread-safe, however, it doesn't ensure thread safe (like synchronized) access to the - * custom data itself. See the top-level documentation of {@link DirectiveCallPlace} to understand the scope and - * life-cycle of the custom data. Be sure that the custom data only depends on things that get their final value - * during template parsing, not on runtime settings. - * + * custom data itself. Be sure that the custom data only depends on things that get their final value during + * template parsing, not on runtime settings. * <p> * This method will block other calls while the {@code supplier} is executing, thus, the object will be * <em>usually</em> created only once, even if multiple threads request the value when it's still {@code null}. It * doesn't stand though when {@code providerIdentity} mismatches occur (see later). Furthermore, then it's also - * possible that multiple objects created by the same {@link CommonSupplier} will be in use on the same time, because - * of directive executions already running in parallel, and because of memory synchronization delays (hardware - * dependent) between the threads. + * possible that multiple objects created by the same {@link CommonSupplier} will be in use on the same time, + * because of directive executions already running in parallel, and because of memory synchronization delays + * (hardware dependent) between the threads. * * @param providerIdentity - * This is usually the class of the {@link TemplateDirectiveModel} that creates (and uses) the custom - * data, or if you are using your own class for the custom data object (as opposed to a class from some - * more generic API), then that class. This is needed as the same call place might calls different - * directives depending on runtime conditions, and so it must be ensured that these directives won't - * accidentally read each other's custom data, ending up with class cast exceptions or worse. In the - * current implementation, if there's a {@code providerIdentity} mismatch (means, the - * {@code providerIdentity} object used when the custom data was last set isn't the exactly same object - * as the one provided with the parameter now), the previous custom data will be just ignored as if it - * was {@code null}. So if multiple directives that use the custom data feature use the same call place, - * the caching of the custom data can be inefficient, as they will keep overwriting each other's custom - * data. (In a more generic implementation the {@code providerIdentity} would be a key in a - * {@link IdentityHashMap}, but then this feature would be slower, while {@code providerIdentity} - * mismatches aren't occurring in most applications.) + * This is usually the class of the {@link TemplateDirectiveModel} that creates (and uses) the custom data, + * or if you are using your own class for the custom data object (as opposed to a class from some more + * generic API), then that class. This is needed as the same call place might calls different directives + * depending on runtime conditions, and so it must be ensured that these directives won't accidentally read + * each other's custom data, ending up with class cast exceptions or worse. In the current implementation, + * if there's a {@code providerIdentity} mismatch (means, the {@code providerIdentity} object used when the + * custom data was last set isn't the exactly same object as the one provided with the parameter now), the + * previous custom data will be just ignored as if it was {@code null}. So if multiple directives that use + * the custom data feature use the same call place, the caching of the custom data can be inefficient, as + * they will keep overwriting each other's custom data. (In a more generic implementation the {@code + * providerIdentity} would be a key in a {@link IdentityHashMap}, but then this feature would be slower, + * while {@code providerIdentity} mismatches aren't occurring in most applications.) * @param supplier - * Called when the custom data wasn't yet set, to invoke its initial value. If this parameter is - * {@code null} and the custom data wasn't set yet, then {@code null} will be returned. The returned - * value of {@link CommonSupplier#get()} can be any kind of object, but can't be {@code null}. + * Called when the custom data wasn't yet set, to invoke its initial value. If this parameter is {@code + * null} and the custom data wasn't set yet, then {@code null} will be returned. The returned value of + * {@link CommonSupplier#get()} can be any kind of object, but can't be {@code null}. * * @return The current custom data object, or possibly {@code null} if there was no {@link CommonSupplier} provided. * * @throws CallPlaceCustomDataInitializationException - * If the {@link CommonSupplier} had to be invoked but failed. + * If the {@link CommonSupplier} had to be invoked but failed. */ Object getOrCreateCustomData(Object providerIdentity, CommonSupplier<?> supplier) throws CallPlaceCustomDataInitializationException; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java index 3588ea7..2f30981 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateCallableModel.java @@ -19,77 +19,11 @@ package org.apache.freemarker.core.model; -import java.util.Collection; - -import org.apache.freemarker.core.util.StringToIndexMap; - /** - * Super interface of {@link TemplateFunctionModel} and {@link TemplateDirectiveModel2}. + * Super interface of {@link TemplateFunctionModel} and {@link TemplateDirectiveModel}. */ public interface TemplateCallableModel extends TemplateModel { - // ------------------------------------------------------------------------------------------------------------- - // Arguments: - - /** - * The highest allowed number of positional arguments, not counting the positional varargs argument. The actual - * positional argument count can be less than this if there are optional positional argument. When calling the - * {@code execute} method, this many items will be reserved for the positional arguments in the argument array (not - * counting the item for the positional varargs argument, if there's one). Positional arguments - * above this count will go to the varargs argument (if there's one, otherwise it's an error). - */ - int getPredefinedPositionalArgumentCount(); - - /** - * Tells if there's no position varargs argument. If there is, then it must be in the argument array at the - * index equals to {@link #getPredefinedPositionalArgumentCount()}. The positional varargs argument is a - * {@link TemplateSequenceModel} that collects all positional arguments whose index would be greater than - * or equal to {@link #getPredefinedPositionalArgumentCount()}. - */ - boolean hasPositionalVarargsArgument(); - - /** - * For the given argument name (that corresponds to a parameter that meant to be passed by name, not by position) - * return its intended index in the {@code args} array argument of the {@code execute} method, or -1 if there's - * no such parameter. Consider using a static final {@link StringToIndexMap} field to implement this. - * - * @return -1 if there's no such named argument - */ - int getPredefinedNamedArgumentIndex(String name); - - /** - * Returns the index of the named varargs argument in the argument array, or -1 if there's no named varargs - * argument. The named varargs argument is a {@link TemplateHashModelEx2} with string keys that collects all - * the named arguments for which {@link #getPredefinedNamedArgumentIndex(String)} returns -1. The iteration order of this - * hash follows the order in which the arguments were specified in the calling template. - * - * @return -1 if there's no named varargs argument - */ - int getNamedVarargsArgumentIndex(); - - /** - * The valid names for arguments that are passed by name (not by position), in the order as they should be displayed - * in error messages, or {@code null} if there's none. If you have implemented - * {@link #getPredefinedNamedArgumentIndex(String)} with a {@link StringToIndexMap}, you should return - * {@link StringToIndexMap#getKeys()} here. - */ - Collection<String> getPredefinedNamedArgumentNames(); - - /** - * The required (minimum) length of the {@code args} array passed to the {@code execute} method. This length always - * includes the space reserved for optional arguments; it's not why it's said to be a minimum length. It's a minimum - * length because a longer array might be reused for better performance (but {@code execute} should never read - * those excess elements). - * The return value should be equal to the sum of these (but we don't want to calculate it on-the-fly, - * for speed), or else {@link IndexOutOfBoundsException}-s might will occur: - * <ul> - * <li>{@link #getPredefinedPositionalArgumentCount()} (note that predefined optional arguments are counted in) - * <li>If {@link #hasPositionalVarargsArgument()} is {@code true}, then 1, else 0. - * <li>Size of {@link #getPredefinedNamedArgumentNames()} (again, predefined optional arguments are counted in) - * <li>If {@link #getNamedVarargsArgumentIndex()} is not -1, then 1, else 0. (Also, obviously, if - * {@link #getNamedVarargsArgumentIndex()} is not -1, then it's one less than the return value of this method.) - * </ul> - */ - int getArgumentArraySize(); + ArgumentArrayLayout getArgumentArrayLayout(); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java deleted file mode 100644 index 787d9d8..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveBody.java +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model; - -import java.io.IOException; -import java.io.Writer; - -import org.apache.freemarker.core.TemplateException; - -/** - * Represents the nested content of a directive ({@link TemplateDirectiveModel}) invocation. An implementation of this - * class is passed to {@link TemplateDirectiveModel#execute(org.apache.freemarker.core.Environment, - * java.util.Map, TemplateModel[], TemplateDirectiveBody)}. The implementation of the method is - * free to invoke it for any number of times, with any writer. - */ -public interface TemplateDirectiveBody { - /** - * Renders the body of the directive body to the specified writer. The - * writer is not flushed after the rendering. If you pass the environment's - * writer, there is no need to flush it. If you supply your own writer, you - * are responsible to flush/close it when you're done with using it (which - * might be after multiple renderings). - * @param out the writer to write the output to. - */ - void render(Writer out) throws TemplateException, IOException; -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java index d9b2f96..6995afe 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel.java @@ -1,67 +1,48 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - package org.apache.freemarker.core.model; import java.io.IOException; -import java.util.Map; +import java.io.Writer; import org.apache.freemarker.core.Environment; import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.util.DeepUnwrap; /** - * "directive" template language data type: used as user-defined directives - * (much like macros) in templates. They can do arbitrary actions, write arbitrary - * text to the template output, and trigger rendering of their nested content for - * any number of times. - * - * <p>They are used in templates like {@code <@myDirective foo=1 bar="wombat">...</@myDirective>} (or as - * {@code <@myDirective foo=1 bar="wombat" />} - the nested content is optional). + * A {@link TemplateCallableModel} that (progressively) prints it result into the {@code out} object, instead of + * returning a single result at the end of the execution. Many of these won't print anything, but has other + * side-effects why it's useful for calling them, or do flow control. They are used in templates like + * {@code <@myDirective foo=1 bar="wombat">...</@myDirective>} (or as {@code <@myDirective foo=1 bar="wombat" />} - + * the nested content is optional). + * <p> + * When called from expression context (and if the template language allows that!), the printed output will be captured, + * and will be the return value of the call. Depending on the output format of the directive, the type of that value + * will be {@link TemplateMarkupOutputModel} or {@link String}. + * <p> + * Note that {@link TemplateDirectiveModel} is a relatively low-level interface that puts more emphasis on + * performance than on ease of implementation. TODO [FM3]: Introduce a more convenient way for implementing directives. */ -public interface TemplateDirectiveModel extends TemplateModel { +public interface TemplateDirectiveModel extends TemplateCallableModel { + /** - * Executes this user-defined directive; called by FreeMarker when the user-defined - * directive is called in the template. + * @param args + * The of argument values. Not {@code null}. If a parameter was omitted on the caller side, the + * corresponding array element will be {@code null}. For the indexed of arguments, see argument array layout + * in the {@link TemplateCallableModel} documentation. + * @param callPlace + * The place (in a template, normally) where this directive was called from. Not {@code null}. Note that + * {@link CallPlace#executeNestedContent(TemplateModel[], Writer, Environment)} can be used to execute the + * nested content. If the directive doesn't support nested content, it should check {@link + * CallPlace#hasNestedContent()} that return {@code false}, and otherwise throw exception. + * @param out + * Print the output here (if there's any) + * @param env + * The current processing environment. Not {@code null}. * - * @param env the current processing environment. Note that you can access - * the output {@link java.io.Writer Writer} by {@link Environment#getOut()}. - * @param params the parameters (if any) passed to the directive as a - * map of key/value pairs where the keys are {@link String}-s and the - * values are {@link TemplateModel} instances. This is never - * <code>null</code>. If you need to convert the template models to POJOs, - * you can use the utility methods in the {@link DeepUnwrap} class. - * @param loopVars an array that corresponds to the "loop variables", in - * the order as they appear in the directive call. ("Loop variables" are out-parameters - * that are available to the nested body of the directive; see in the Manual.) - * You set the loop variables by writing this array. The length of the array gives the - * number of loop-variables that the caller has specified. - * Never <code>null</code>, but can be a zero-length array. - * @param body an object that can be used to render the nested content (body) of - * the directive call. If the directive call has no nested content (i.e., it's like - * <@myDirective /> or <@myDirective></@myDirective>), then this will be - * <code>null</code>. - * - * @throws TemplateException If any problem occurs that's not an {@link IOException} during writing the template - * output. - * @throws IOException When writing the template output fails. + * @throws TemplateException + * If any problem occurs that's not an {@link IOException} during writing the template output. + * @throws IOException + * When writing the template output fails. */ - void execute(Environment env, Map params, TemplateModel[] loopVars, - TemplateDirectiveBody body) throws TemplateException, IOException; -} \ No newline at end of file + void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) + throws TemplateException, IOException; + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java deleted file mode 100644 index 75bf2f2..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateDirectiveModel2.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.apache.freemarker.core.model; - -import java.io.IOException; -import java.io.Writer; - -import org.apache.freemarker.core.Environment; -import org.apache.freemarker.core.TemplateException; - -/** - * A {@link TemplateCallableModel} that (progressively) prints it result into the {@code out} object, instead of - * returning a single result at the end of the execution. Many of these won't print anything, but has other - * side-effects why it's useful for calling them, or do flow control. They are used in templates like - * {@code <@myDirective foo=1 bar="wombat">...</@myDirective>} (or as {@code <@myDirective foo=1 bar="wombat" />} - - * the nested content is optional). - * <p> - * When called from expression context (and if the template language allows that!), the printed output will be captured, - * and will be the return value of the call. Depending on the output format of the directive, the type of that value - * will be {@link TemplateMarkupOutputModel} or {@link String}. - */ -// TODO [FM3][CF] Rename this to TemplateDirectiveModel -public interface TemplateDirectiveModel2 extends TemplateCallableModel { - - /** - * @param args - * Array with {@link #getArgumentArraySize()} elements (or more, in which case the extra elements should be - * ignored). Not {@code null}. If a parameter was omitted on the caller side, the corresponding array - * element will be {@code null}. Parameters passed by position will be at the index that corresponds to the - * position (the 1st argument is at index 0). However, positional parameters over {@link - * #getPredefinedPositionalArgumentCount()} will be in the positional varargs sequence at index one higher, - * assuming {@link #hasPositionalVarargsArgument()} is {@code true}. Parameters passed by name (rather than - * by position) will be at the index returned be {@link #getPredefinedNamedArgumentIndex(String)}, or in the - * named varargs hash at index {@link #getNamedVarargsArgumentIndex()}, assuming that's not -1. - * @param callPlace - * The place (in a template, normally) where this directive was called from. Not {@code null}. Note that - * {@link CallPlace#executeNestedContent(TemplateModel[], Writer, Environment)} can be used to execute the - * nested content. If the directive doesn't support nested content, it should check {@link - * CallPlace#hasNestedContent()} that return {@code false}, and otherwise throw exception. - * @param out - * Print the output here (if there's any) - * @param env - * The current processing environment. Not {@code null}. - * - * @throws TemplateException - * If any problem occurs that's not an {@link IOException} during writing the template output. - * @throws IOException - * When writing the template output fails. - */ - void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) - throws TemplateException, IOException; - -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java index 7944d81..2ddfca0 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateFunctionModel.java @@ -7,7 +7,7 @@ import org.apache.freemarker.core.outputformat.OutputFormat; /** * A {@link TemplateCallableModel}, which returns its result as a {@link TemplateModel} at the end of the execution. - * This is in contrast with {@link TemplateDirectiveModel2}, which writes its result progressively to the output. + * This is in contrast with {@link TemplateDirectiveModel}, which writes its result progressively to the output. * * <p>Some template languages may allow function calls directly embedded into static text, as in * <code>text#f()text</code>. In that case, the language has to ensure that the return value is formatted according http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java deleted file mode 100644 index 789d9bb..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateTransformModel.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model; - -import java.io.IOException; -import java.io.Writer; -import java.util.Map; - -import org.apache.freemarker.core.util.DeepUnwrap; - -/** - * "transform" template language data type: user-defined directives - * (much like macros) specialized on filtering output; you should rather use the newer {@link TemplateDirectiveModel} - * instead. This certainly will be deprecated in FreeMarker 2.4. - */ -public interface TemplateTransformModel extends TemplateModel { - - /** - * Returns a writer that will be used by the engine to feed the - * transformation input to the transform. Each call to this method - * must return a new instance of the writer so that the transformation - * is thread-safe. - * @param out the character stream to which to write the transformed output - * @param args the arguments (if any) passed to the transformation as a - * map of key/value pairs where the keys are strings and the arguments are - * TemplateModel instances. This is never null. If you need to convert the - * template models to POJOs, you can use the utility methods in the - * {@link DeepUnwrap} class. - * @return a writer to which the engine will feed the transformation - * input, or null if the transform does not support nested content (body). - * The returned writer can implement the {@link TransformControl} - * interface if it needs advanced control over the evaluation of the - * transformation body. - */ - Writer getWriter(Writer out, Map args) - throws TemplateModelException, IOException; -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java deleted file mode 100644 index cd3965c..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TransformControl.java +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you under the Apache License, Version 2.0 (the - * "License"); you may not use this file except in compliance - * with the License. You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -package org.apache.freemarker.core.model; - -import java.io.IOException; - -import org.apache.freemarker.core.TemplateException; - -/** - * An interface that can be implemented by writers returned from - * {@link TemplateTransformModel#getWriter(java.io.Writer, java.util.Map)}. The - * methods on this - * interfaces are callbacks that will be called by the template engine and that - * give the writer a chance to better control the evaluation of the transform - * body. The writer can instruct the engine to skip or to repeat body - * evaluation, and gets notified about exceptions that are thrown during the - * body evaluation. - */ -public interface TransformControl { - /** - * Constant returned from {@link #afterBody()} that tells the - * template engine to repeat transform body evaluation and feed - * it again to the transform. - */ - int REPEAT_EVALUATION = 0; - - /** - * Constant returned from {@link #afterBody()} that tells the - * template engine to end the transform and close the writer. - */ - int END_EVALUATION = 1; - - /** - * Constant returned from {@link #onStart()} that tells the - * template engine to skip evaluation of the body. - */ - int SKIP_BODY = 0; - - /** - * Constant returned from {@link #onStart()} that tells the - * template engine to evaluate the body. - */ - int EVALUATE_BODY = 1; - - /** - * Called before the body is evaluated for the first time. - * @return - * <ul> - * <li><tt>SKIP_BODY</tt> if the transform wants to ignore the body. In this - * case, only {@link java.io.Writer#close()} is called next and processing ends.</li> - * <li><tt>EVALUATE_BODY</tt> to normally evaluate the body of the transform - * and feed it to the writer</li> - * </ul> - */ - int onStart() throws TemplateModelException, IOException; - - /** - * Called after the body has been evaluated. - * @return - * <ul> - * <li><tt>END_EVALUATION</tt> if the transformation should be ended.</li> - * <li><tt>REPEAT_EVALUATION</tt> to have the engine re-evaluate the - * transform body and feed it again to the writer.</li> - * </ul> - */ - int afterBody() throws TemplateModelException, IOException; - - /** - * Called if any exception occurs during the transform between the - * {@link TemplateTransformModel#getWriter(java.io.Writer, java.util.Map)} call - * and the {@link java.io.Writer#close()} call. - * @param t the throwable that represents the exception. It can be any - * non-checked throwable, as well as {@link TemplateException} and - * {@link java.io.IOException}. - * - * @throws Throwable is recommended that the methods rethrow the received - * throwable. If the method wants to throw another throwable, it should - * either throw a non-checked throwable, or an instance of - * {@link TemplateException} and {@link java.io.IOException}. Throwing any - * other checked exception will cause the engine to rethrow it as - * a {@link java.lang.reflect.UndeclaredThrowableException}. - */ - void onError(Throwable t) throws Throwable; -} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java index 1da4f62..647ad90 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/FTLUtil.java @@ -40,7 +40,6 @@ import org.apache.freemarker.core.model.TemplateNodeModelEx; import org.apache.freemarker.core.model.TemplateNumberModel; import org.apache.freemarker.core.model.TemplateScalarModel; import org.apache.freemarker.core.model.TemplateSequenceModel; -import org.apache.freemarker.core.model.TemplateTransformModel; import org.apache.freemarker.core.model.WrapperTemplateModel; import org.apache.freemarker.core.model.impl.BeanAndStringModel; import org.apache.freemarker.core.model.impl.BeanModel; @@ -774,8 +773,6 @@ public final class FTLUtil { if (TemplateDirectiveModel.class.isAssignableFrom(cl)) { appendTypeName(sb, typeNamesAppended, "directive"); - } else if (TemplateTransformModel.class.isAssignableFrom(cl)) { - appendTypeName(sb, typeNamesAppended, "transform"); } if (TemplateSequenceModel.class.isAssignableFrom(cl)) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java index 55b7d5d..9edb980 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java @@ -28,6 +28,8 @@ import java.util.Map; * Maps string keys to non-negative int-s. This isn't a {@link Map}, but a more specialized class. It's immutable. * It's slower to create than {@link HashMap}, but usually is a bit faster to read, and when {@link HashMap} gets * unlucky with clashing hash keys, then it can be significantly faster. + * <p> + * Instances of this class are immutable, thread-safe objects. */ public final class StringToIndexMap { @@ -239,6 +241,31 @@ public final class StringToIndexMap { return keys != null ? keys.size() : 1; } + /** + * Checks if all entries are in the {@code start} - {@code start}+{@code size()} (exclusive) index range. + * + * @throws IllegalArgumentException If some entry is not in the specified index range. + */ + public void checkIndexRange(int start) { + if (buckets == null) { + return; + } + + int end = start + size(); + for (Entry bucket : buckets) { + Entry entry = bucket; + while (entry != null) { + if (entry.value < start || entry.value >= end) { + throw new IllegalArgumentException( + "Entry " + _StringUtil.jQuote(entry.key) + " -> " + entry.value + + " is out of allowed index range: " + start + " ..< " + end); + } + entry = entry.nextInSameBucket; + } + } + + } + private static int getPowerOf2GreaterThanOrEqualTo(int n) { if (n == 0) { return 0; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-core/src/main/javacc/FTL.jj ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/javacc/FTL.jj b/freemarker-core/src/main/javacc/FTL.jj index c42c7a9..9de0521 100644 --- a/freemarker-core/src/main/javacc/FTL.jj +++ b/freemarker-core/src/main/javacc/FTL.jj @@ -35,7 +35,7 @@ import org.apache.freemarker.core.outputformat.impl.*; import org.apache.freemarker.core.model.*; import org.apache.freemarker.core.model.impl.*; import org.apache.freemarker.core.util.*; -import org.apache.freemarker.core.ASTDirDynamicCall.NamedArgument; +import org.apache.freemarker.core.ASTDynamicTopLevelCall.NamedArgument; import java.io.*; import java.util.*; import java.nio.charset.Charset; @@ -569,7 +569,7 @@ TOKEN_MGR_DECLS: return image.charAt(idx + charIdxInName); } - private void unifiedCall(Token tok) { + private void dynamicTopLevelCall(Token tok) { char firstChar = tok.image.charAt(0); if (autodetectTagSyntax && !directiveSyntaxEstablished) { squBracTagSyntax = (firstChar == '['); @@ -586,7 +586,7 @@ TOKEN_MGR_DECLS: SwitchTo(NO_SPACE_EXPRESSION); } - private void unifiedCallEnd(Token tok) { + private void dynamicTopLevelCallEnd(Token tok) { char firstChar = tok.image.charAt(0); if (squBracTagSyntax && firstChar == '<') { tok.kind = STATIC_TEXT_NON_WS; @@ -846,13 +846,9 @@ TOKEN: handleTagSyntaxAndSwitch(matchedToken, DEFAULT); } | - <UNIFIED_CALL : "<@" | "[@" > { unifiedCall(matchedToken); } + <DYNAMIC_TOP_LEVEL_CALL : "<@" | "[@" > { dynamicTopLevelCall(matchedToken); } | - <UNIFIED_CALL_END : ("<" | "[") "/@" ((<ID>) ("."<ID>)*)? <CLOSE_TAG1>> { unifiedCallEnd(matchedToken); } - | - <DYNAMIC_DIRECTIVE_CALL : "<~" | "[~" > { unifiedCall(matchedToken); } - | - <DYNAMIC_DIRECTIVE_CALL_END : ("<" | "[") "/~" ((<ID>) ("."<ID>)*)? <CLOSE_TAG1>> { unifiedCallEnd(matchedToken); } + <DYNAMIC_TOP_LEVEL_CALL_END : ("<" | "[") "/@" ((<ID>) ("."<ID>)*)? <CLOSE_TAG1>> { dynamicTopLevelCallEnd(matchedToken); } | <FTL_HEADER : ("<#ftl" | "[#ftl") <BLANK>> { ftlHeader(matchedToken); } | @@ -3034,102 +3030,7 @@ ASTDirCompress Compress() : } } -ASTElement UnifiedMacroTransform() : -{ - Token start = null, end, t; - HashMap namedArgs = null; - ArrayList positionalArgs = null, bodyParameters = null; - ASTExpression startTagNameExp; - TemplateElements children; - ASTExpression exp; - int pushedCtxCount = 0; -} -{ - start = <UNIFIED_CALL> - exp = ASTExpression() - { - if (exp instanceof ASTExpVariable || (exp instanceof ASTExpDot && ((ASTExpDot) exp).onlyHasIdentifiers())) { - startTagNameExp = exp; - } else { - startTagNameExp = null; - } - } - [<TERMINATING_WHITESPACE>] - ( - LOOKAHEAD(<ID><ASSIGNMENT_EQUALS>) - namedArgs = NamedArgs() - | - positionalArgs = PositionalArgs() - ) - [ - <SEMICOLON>{bodyParameters = new ArrayList(4); } - [ - t = <ID> { bodyParameters.add(t.image); } - ( - <COMMA> - t = <ID>{bodyParameters.add(t.image); } - )* - ] - ] - ( - end = <EMPTY_DIRECTIVE_END> { children = TemplateElements.EMPTY; } - | - ( - <DIRECTIVE_END> { - if (bodyParameters != null && iteratorBlockContexts != null && !iteratorBlockContexts.isEmpty()) { - // It's possible that we shadow a #list/#items loop variable, in which case that must be noted. - int ctxsLen = iteratorBlockContexts.size(); - int bodyParsLen = bodyParameters.size(); - for (int bodyParIdx = 0; bodyParIdx < bodyParsLen; bodyParIdx++) { - String bodyParName = (String) bodyParameters.get(bodyParIdx); - walkCtxSack: for (int ctxIdx = ctxsLen - 1; ctxIdx >= 0; ctxIdx--) { - ParserIteratorBlockContext ctx - = (ParserIteratorBlockContext) iteratorBlockContexts.get(ctxIdx); - if (ctx.loopVarName != null && ctx.loopVarName.equals(bodyParName)) { - // If it wasn't already shadowed, shadow it: - if (ctx.kind != ITERATOR_BLOCK_KIND_USER_DIRECTIVE) { - ParserIteratorBlockContext shadowingCtx = pushIteratorBlockContext(); - shadowingCtx.loopVarName = bodyParName; - shadowingCtx.kind = ITERATOR_BLOCK_KIND_USER_DIRECTIVE; - pushedCtxCount++; - } - break walkCtxSack; - } - } - } - } - } - children = MixedContentElements() - end = <UNIFIED_CALL_END> - { - for (int i = 0; i < pushedCtxCount; i++) { - popIteratorBlockContext(); - } - - String endTagName = end.image.substring(3, end.image.length() - 1).trim(); - if (endTagName.length() > 0) { - if (startTagNameExp == null) { - throw new ParseException("Expecting </@>", template, end); - } else { - String startTagName = startTagNameExp.getCanonicalForm(); - if (!endTagName.equals(startTagName)) { - throw new ParseException("Expecting </@> or </@" + startTagName + ">", template, end); - } - } - } - } - ) - ) - { - ASTElement result = (positionalArgs != null) - ? new ASTDirUserDefined(exp, positionalArgs, children, bodyParameters) - : new ASTDirUserDefined(exp, namedArgs, children, bodyParameters); - result.setLocation(template, start, end); - return result; - } -} - -ASTElement DynamicDirectiveCall() : +ASTElement DynamicTopLevelCall() : { Token t; ASTExpression exp; @@ -3142,7 +3043,7 @@ ASTElement DynamicDirectiveCall() : int pushedCtxCount = 0; } { - start = <DYNAMIC_DIRECTIVE_CALL> + start = <DYNAMIC_TOP_LEVEL_CALL> callableValueExp = ASTExpression() [<TERMINATING_WHITESPACE>] { @@ -3278,7 +3179,7 @@ ASTElement DynamicDirectiveCall() : children = MixedContentElements() - end = <DYNAMIC_DIRECTIVE_CALL_END> + end = <DYNAMIC_TOP_LEVEL_CALL_END> { for (int i = 0; i < pushedCtxCount; i++) { popIteratorBlockContext(); @@ -3301,7 +3202,7 @@ ASTElement DynamicDirectiveCall() : ) ) { - ASTElement result = new ASTDirDynamicCall( + ASTElement result = new ASTDynamicTopLevelCall( callableValueExp, false, trimmedPositionalArgs, trimmedNamedArgs, loopVarNames, children); @@ -3715,9 +3616,7 @@ ASTElement FreemarkerDirective() : | tp = Compress() | - tp = UnifiedMacroTransform() - | - tp = DynamicDirectiveCall() + tp = DynamicTopLevelCall() | tp = Items() | http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java ---------------------------------------------------------------------- diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java index 608f1bb..c6adead 100644 --- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java +++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/IncludePage.java @@ -40,12 +40,14 @@ import org.apache.freemarker.core.Environment; import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core._DelayedFTLTypeDescription; import org.apache.freemarker.core._MiscTemplateException; +import org.apache.freemarker.core.model.ArgumentArrayLayout; +import org.apache.freemarker.core.model.CallPlace; import org.apache.freemarker.core.model.TemplateBooleanModel; -import org.apache.freemarker.core.model.TemplateDirectiveBody; import org.apache.freemarker.core.model.TemplateDirectiveModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateScalarModel; import org.apache.freemarker.core.util.DeepUnwrap; +import org.apache.freemarker.core.util.StringToIndexMap; /** @@ -60,26 +62,45 @@ import org.apache.freemarker.core.util.DeepUnwrap; * values of parameters. */ public class IncludePage implements TemplateDirectiveModel { + private final HttpServletRequest request; private final HttpServletResponse response; + + private static final int PATH_PARAM_IDX = 0; + private static final int INHERIT_PARAMS_PARAM_IDX = 1; + private static final int PARAMS_PARAM_IDX = 2; + + private static final String PATH_PARAM_NAME = "path"; + private static final String INHERIT_PARAMS_PARAM_NAME = "inherit_params"; + private static final String PARAMS_PARAM_NAME = "params"; + + private static final StringToIndexMap NAME_TO_IDX_MAP = StringToIndexMap.of( + PATH_PARAM_NAME, PATH_PARAM_IDX, + INHERIT_PARAMS_PARAM_NAME, INHERIT_PARAMS_PARAM_IDX + ); + + private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create( + 0, false, + NAME_TO_IDX_MAP, false + ); public IncludePage(HttpServletRequest request, HttpServletResponse response) { this.request = request; this.response = response; } - + @Override - public void execute(final Environment env, Map params, - TemplateModel[] loopVars, TemplateDirectiveBody body) - throws TemplateException, IOException { + public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) + throws TemplateException, IOException { // Determine the path - final TemplateModel path = (TemplateModel) params.get("path"); + final TemplateModel path = args[PATH_PARAM_IDX]; if (path == null) { throw new _MiscTemplateException(env, "Missing required parameter \"path\""); } if (!(path instanceof TemplateScalarModel)) { throw new _MiscTemplateException(env, - "Expected a scalar model. \"path\" is instead ", new _DelayedFTLTypeDescription(path)); + "Expected a scalar model. \"", PATH_PARAM_NAME, "\" is instead ", + new _DelayedFTLTypeDescription(path)); } final String strPath = ((TemplateScalarModel) path).getAsString(); if (strPath == null) { @@ -113,21 +134,21 @@ public class IncludePage implements TemplateDirectiveModel { // Determine inherit_params value final boolean inheritParams; - final TemplateModel inheritParamsModel = (TemplateModel) params.get("inherit_params"); + final TemplateModel inheritParamsModel = args[INHERIT_PARAMS_PARAM_IDX]; if (inheritParamsModel == null) { // defaults to true when not specified inheritParams = true; } else { if (!(inheritParamsModel instanceof TemplateBooleanModel)) { throw new _MiscTemplateException(env, - "\"inherit_params\" should be a boolean but it's a(n) ", + "\"", INHERIT_PARAMS_PARAM_NAME, "\" should be a boolean but it's a(n) ", inheritParamsModel.getClass().getName(), " instead"); } inheritParams = ((TemplateBooleanModel) inheritParamsModel).getAsBoolean(); } // Get explicit params, if any - final TemplateModel paramsModel = (TemplateModel) params.get("params"); + final TemplateModel paramsModel = args[PARAMS_PARAM_IDX]; // Determine whether we need to wrap the request final HttpServletRequest wrappedRequest; @@ -143,7 +164,7 @@ public class IncludePage implements TemplateDirectiveModel { final Object unwrapped = DeepUnwrap.unwrap(paramsModel); if (!(unwrapped instanceof Map)) { throw new _MiscTemplateException(env, - "Expected \"params\" to unwrap into a java.util.Map. It unwrapped into ", + "Expected \"", PARAMS_PARAM_NAME, "\" to unwrap into a java.util.Map. It unwrapped into ", unwrapped.getClass().getName(), " instead."); } paramsMap = (Map) unwrapped; @@ -163,6 +184,11 @@ public class IncludePage implements TemplateDirectiveModel { } } + @Override + public ArgumentArrayLayout getArgumentArrayLayout() { + return ARGS_LAYOUT; + } + private static final class CustomParamsRequest extends HttpServletRequestWrapper { private final HashMap paramsMap; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java ---------------------------------------------------------------------- diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java new file mode 100644 index 0000000..af0a8e0 --- /dev/null +++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/BodyContentImpl.java @@ -0,0 +1,222 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +package org.apache.freemarker.servlet.jsp; + +import java.io.CharArrayReader; +import java.io.CharArrayWriter; +import java.io.IOException; +import java.io.Reader; +import java.io.Writer; + +import javax.servlet.jsp.JspWriter; +import javax.servlet.jsp.tagext.BodyContent; + +/** + * An implementation of BodyContent that buffers it's input to a char[]. + */ +class BodyContentImpl extends BodyContent { + private CharArrayWriter buf; + + BodyContentImpl(JspWriter out, boolean buffer) { + super(out); + if (buffer) initBuffer(); + } + + void initBuffer() { + buf = new CharArrayWriter(); + } + + @Override + public void flush() throws IOException { + if (buf == null) { + getEnclosingWriter().flush(); + } + } + + @Override + public void clear() throws IOException { + if (buf != null) { + buf = new CharArrayWriter(); + } else { + throw new IOException("Can't clear"); + } + } + + @Override + public void clearBuffer() throws IOException { + if (buf != null) { + buf = new CharArrayWriter(); + } else { + throw new IOException("Can't clear"); + } + } + + @Override + public int getRemaining() { + return Integer.MAX_VALUE; + } + + @Override + public void newLine() throws IOException { + write(JspWriterAdapter.NEWLINE); + } + + @Override + public void close() throws IOException { + } + + @Override + public void print(boolean arg0) throws IOException { + write(arg0 ? Boolean.TRUE.toString() : Boolean.FALSE.toString()); + } + + @Override + public void print(char arg0) throws IOException { + write(arg0); + } + + @Override + public void print(char[] arg0) throws IOException { + write(arg0); + } + + @Override + public void print(double arg0) throws IOException { + write(Double.toString(arg0)); + } + + @Override + public void print(float arg0) throws IOException { + write(Float.toString(arg0)); + } + + @Override + public void print(int arg0) throws IOException { + write(Integer.toString(arg0)); + } + + @Override + public void print(long arg0) throws IOException { + write(Long.toString(arg0)); + } + + @Override + public void print(Object arg0) throws IOException { + write(arg0 == null ? "null" : arg0.toString()); + } + + @Override + public void print(String arg0) throws IOException { + write(arg0); + } + + @Override + public void println() throws IOException { + newLine(); + } + + @Override + public void println(boolean arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(char arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(char[] arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(double arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(float arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(int arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(long arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(Object arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void println(String arg0) throws IOException { + print(arg0); + newLine(); + } + + @Override + public void write(int c) throws IOException { + if (buf != null) { + buf.write(c); + } else { + getEnclosingWriter().write(c); + } + } + + @Override + public void write(char[] cbuf, int off, int len) throws IOException { + if (buf != null) { + buf.write(cbuf, off, len); + } else { + getEnclosingWriter().write(cbuf, off, len); + } + } + + @Override + public String getString() { + return buf.toString(); + } + + @Override + public Reader getReader() { + return new CharArrayReader(buf.toCharArray()); + } + + @Override + public void writeOut(Writer out) throws IOException { + buf.writeTo(out); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java ---------------------------------------------------------------------- diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java index cfb91b6..c51e611 100644 --- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java +++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/CustomTagAndELFunctionCombiner.java @@ -21,17 +21,16 @@ package org.apache.freemarker.servlet.jsp; import java.io.IOException; import java.io.Writer; import java.util.List; -import java.util.Map; import org.apache.freemarker.core.Environment; import org.apache.freemarker.core.TemplateException; import org.apache.freemarker.core._UnexpectedTypeErrorExplainerTemplateModel; -import org.apache.freemarker.core.model.TemplateDirectiveBody; +import org.apache.freemarker.core.model.ArgumentArrayLayout; +import org.apache.freemarker.core.model.CallPlace; import org.apache.freemarker.core.model.TemplateDirectiveModel; import org.apache.freemarker.core.model.TemplateMethodModelEx; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelException; -import org.apache.freemarker.core.model.TemplateTransformModel; import org.apache.freemarker.core.model.impl.JavaMethodModel; import org.apache.freemarker.core.util.BugException; import org.apache.freemarker.core.util._ClassUtil; @@ -46,7 +45,7 @@ class CustomTagAndELFunctionCombiner { /** * @param customTag - * Either a {@link TemplateDirectiveModel} or a {@link TemplateTransformModel}. + * A {@link TemplateDirectiveModel}. */ static TemplateModel combine(TemplateModel customTag, TemplateMethodModelEx elFunction) { if (customTag instanceof TemplateDirectiveModel) { @@ -55,12 +54,6 @@ class CustomTagAndELFunctionCombiner { (TemplateDirectiveModel) customTag, (JavaMethodModel) elFunction) // : new TemplateDirectiveModelAndTemplateMethodModelEx( // (TemplateDirectiveModel) customTag, elFunction); - } else if (customTag instanceof TemplateTransformModel) { - return (elFunction instanceof JavaMethodModel) - ? new TemplateTransformModelAndSimpleMethodModel( // - (TemplateTransformModel) customTag, (JavaMethodModel) elFunction) // - : new TemplateTransformModelAndTemplateMethodModelEx( // - (TemplateTransformModel) customTag, elFunction); } else { throw new BugException( "Unexpected custom JSP tag class: " + _ClassUtil.getShortClassNameOfObject(customTag)); @@ -72,8 +65,7 @@ class CustomTagAndELFunctionCombiner { * {@link #combine(TemplateModel, TemplateMethodModelEx)}. */ static boolean canBeCombinedAsCustomTag(TemplateModel tm) { - return (tm instanceof TemplateDirectiveModel || tm instanceof TemplateTransformModel) - && !(tm instanceof CombinedTemplateModel); + return (tm instanceof TemplateDirectiveModel) && !(tm instanceof CombinedTemplateModel); } /** @@ -107,16 +99,20 @@ class CustomTagAndELFunctionCombiner { } @Override - public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) - throws TemplateException, IOException { - templateDirectiveModel.execute(env, params, loopVars, body); + public Object[] explainTypeError(Class[] expectedClasses) { + return simpleMethodModel.explainTypeError(expectedClasses); } @Override - public Object[] explainTypeError(Class[] expectedClasses) { - return simpleMethodModel.explainTypeError(expectedClasses); + public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) + throws TemplateException, IOException { + templateDirectiveModel.execute(args, callPlace, out, env); } + @Override + public ArgumentArrayLayout getArgumentArrayLayout() { + return templateDirectiveModel.getArgumentArrayLayout(); + } } private static class TemplateDirectiveModelAndTemplateMethodModelEx extends CombinedTemplateModel @@ -137,64 +133,15 @@ class CustomTagAndELFunctionCombiner { } @Override - public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) + public void execute(TemplateModel[] args, CallPlace callPlace, Writer out, Environment env) throws TemplateException, IOException { - templateDirectiveModel.execute(env, params, loopVars, body); - } - - } - - private static class TemplateTransformModelAndTemplateMethodModelEx extends CombinedTemplateModel - implements TemplateTransformModel, TemplateMethodModelEx { - - private final TemplateTransformModel templateTransformModel; - private final TemplateMethodModelEx templateMethodModelEx; - - public TemplateTransformModelAndTemplateMethodModelEx( // - TemplateTransformModel templateTransformModel, TemplateMethodModelEx templateMethodModelEx) { - this.templateTransformModel = templateTransformModel; - this.templateMethodModelEx = templateMethodModelEx; + templateDirectiveModel.execute(args, callPlace, out, env); } @Override - public Object exec(List arguments) throws TemplateModelException { - return templateMethodModelEx.exec(arguments); + public ArgumentArrayLayout getArgumentArrayLayout() { + return templateDirectiveModel.getArgumentArrayLayout(); } - - @Override - public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException { - return templateTransformModel.getWriter(out, args); - } - - } - - private static class TemplateTransformModelAndSimpleMethodModel extends CombinedTemplateModel - implements TemplateTransformModel, TemplateMethodModelEx, _UnexpectedTypeErrorExplainerTemplateModel { - - private final TemplateTransformModel templateTransformModel; - private final JavaMethodModel simpleMethodModel; - - public TemplateTransformModelAndSimpleMethodModel( // - TemplateTransformModel templateTransformModel, JavaMethodModel simpleMethodModel) { - this.templateTransformModel = templateTransformModel; - this.simpleMethodModel = simpleMethodModel; - } - - @Override - public Object exec(List arguments) throws TemplateModelException { - return simpleMethodModel.exec(arguments); - } - - @Override - public Object[] explainTypeError(Class[] expectedClasses) { - return simpleMethodModel.explainTypeError(expectedClasses); - } - - @Override - public Writer getWriter(Writer out, Map args) throws TemplateModelException, IOException { - return templateTransformModel.getWriter(out, args); - } - } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java ---------------------------------------------------------------------- diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java index 4564d71..80f3f2b 100644 --- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java +++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/FreeMarkerPageContext.java @@ -387,7 +387,7 @@ abstract class FreeMarkerPageContext extends PageContext implements TemplateMode @Override public BodyContent pushBody() { - return (BodyContent) pushWriter(new TagTransformModel.BodyContentImpl(getOut(), true)); + return (BodyContent) pushWriter(new BodyContentImpl(getOut(), true)); } @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java ---------------------------------------------------------------------- diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java index 8c6fd3a..99e8272 100644 --- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java +++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java @@ -27,7 +27,6 @@ import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.math.BigDecimal; import java.util.HashMap; -import java.util.Iterator; import java.util.Map; import org.apache.freemarker.core.Template; @@ -36,8 +35,9 @@ import org.apache.freemarker.core._DelayedShortClassName; import org.apache.freemarker.core._ErrorDescriptionBuilder; import org.apache.freemarker.core._TemplateModelException; import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper; -import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateHashModelEx2; import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateScalarModel; import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; import org.apache.freemarker.core.util._StringUtil; import org.apache.freemarker.servlet.jsp.SimpleTagDirectiveModel.TemplateExceptionWrapperJspException; @@ -75,17 +75,17 @@ class JspTagModelBase { return tagClass.newInstance(); } - void setupTag(Object tag, Map args, ObjectWrapperAndUnwrapper wrapper) - throws TemplateModelException, + void setupTag(Object tag, TemplateHashModelEx2 args, ObjectWrapperAndUnwrapper wrapper) + throws TemplateModelException, InvocationTargetException, IllegalAccessException { if (args != null && !args.isEmpty()) { final Object[] argArray = new Object[1]; - for (Iterator iter = args.entrySet().iterator(); iter.hasNext(); ) { - final Map.Entry entry = (Map.Entry) iter.next(); - final Object arg = wrapper.unwrap((TemplateModel) entry.getValue()); + for (TemplateHashModelEx2.KeyValuePairIterator iter = args.keyValuePairIterator(); iter.hasNext(); ) { + final TemplateHashModelEx2.KeyValuePair entry = iter.next(); + final Object arg = wrapper.unwrap(entry.getValue()); argArray[0] = arg; - final Object paramName = entry.getKey(); + final String paramName = ((TemplateScalarModel) entry.getKey()).getAsString(); Method setterMethod = (Method) propertySetters.get(paramName); if (setterMethod == null) { if (dynaSetter == null) { @@ -130,7 +130,8 @@ class JspTagModelBase { } } - protected final TemplateModelException toTemplateModelExceptionOrRethrow(Exception e) throws TemplateModelException { + protected final TemplateModelException toTemplateModelExceptionOrRethrow(Throwable e) + throws TemplateModelException { if (e instanceof RuntimeException && !isCommonRuntimeException((RuntimeException) e)) { throw (RuntimeException) e; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/52a5f9eb/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java ---------------------------------------------------------------------- diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java index 0e01e46..810a50c 100644 --- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java +++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/SimpleTagDirectiveModel.java @@ -22,7 +22,6 @@ package org.apache.freemarker.servlet.jsp; import java.beans.IntrospectionException; import java.io.IOException; import java.io.Writer; -import java.util.Map; import javax.servlet.jsp.JspContext; import javax.servlet.jsp.JspException; @@ -33,15 +32,22 @@ import javax.servlet.jsp.tagext.Tag; import org.apache.freemarker.core.Environment; import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.TemplateDirectiveBody; +import org.apache.freemarker.core.model.ArgumentArrayLayout; +import org.apache.freemarker.core.model.CallPlace; import org.apache.freemarker.core.model.TemplateDirectiveModel; +import org.apache.freemarker.core.model.TemplateHashModelEx2; import org.apache.freemarker.core.model.TemplateModel; /** * Adapts a {@link SimpleTag}-based custom JSP tag to be a value that's callable in templates as an user-defined - * directive. For {@link Tag}-based custom JSP tags {@link TagTransformModel} is used instead. + * directive. For {@link Tag}-based custom JSP tags {@link TagDirectiveModel} is used instead. */ class SimpleTagDirectiveModel extends JspTagModelBase implements TemplateDirectiveModel { + + private static final ArgumentArrayLayout ARGS_LAYOUT = ArgumentArrayLayout.create( + 0, false, + null, true); + protected SimpleTagDirectiveModel(String tagName, Class tagClass) throws IntrospectionException { super(tagName, tagClass); if (!SimpleTag.class.isAssignableFrom(tagClass)) { @@ -53,9 +59,8 @@ class SimpleTagDirectiveModel extends JspTagModelBase implements TemplateDirecti } @Override - public void execute(Environment env, Map args, TemplateModel[] outArgs, - final TemplateDirectiveBody body) - throws TemplateException, IOException { + public void execute(TemplateModel[] args, final CallPlace callPlace, Writer out, final Environment env) + throws TemplateException, IOException { try { SimpleTag tag = (SimpleTag) getTagInstance(); final FreeMarkerPageContext pageContext = PageContextFactory.getCurrentPageContext(); @@ -66,8 +71,9 @@ class SimpleTagDirectiveModel extends JspTagModelBase implements TemplateDirecti if (parentTag != null) { tag.setParent(parentTag); } - setupTag(tag, args, pageContext.getObjectWrapper()); - if (body != null) { + setupTag(tag, (TemplateHashModelEx2) args[ARGS_LAYOUT.getNamedVarargsArgumentIndex()], + pageContext.getObjectWrapper()); + if (callPlace.hasNestedContent()) { tag.setJspBody(new JspFragment() { @Override public JspContext getJspContext() { @@ -77,7 +83,7 @@ class SimpleTagDirectiveModel extends JspTagModelBase implements TemplateDirecti @Override public void invoke(Writer out) throws JspException, IOException { try { - body.render(out == null ? pageContext.getOut() : out); + callPlace.executeNestedContent(null, out == null ? pageContext.getOut() : out, env); } catch (TemplateException e) { throw new TemplateExceptionWrapperJspException(e); } @@ -99,7 +105,12 @@ class SimpleTagDirectiveModel extends JspTagModelBase implements TemplateDirecti throw toTemplateModelExceptionOrRethrow(e); } } - + + @Override + public ArgumentArrayLayout getArgumentArrayLayout() { + return ARGS_LAYOUT; + } + static final class TemplateExceptionWrapperJspException extends JspException { public TemplateExceptionWrapperJspException(Throwable cause) {