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><transform></tt> block will process the generated template * just as if it had been <tt><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; } }