/*
* Copyright 2014 Attila Szegedi, Daniel Dekany, Jonathan Revusky
*
* Licensed 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 freemarker.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;
/**
* The local variables and such of an FTL macro or FTL function (or other future FTL callable) call.
*/
class CallableInvocationContext implements LocalContext {
final UnboundCallable callableDefinition;
final Environment.Namespace localVars;
final TemplateElement nestedContent;
final Environment.Namespace nestedContentNamespace;
final Template nestedContentTemplate;
final List nestedContentParameterNames;
final ArrayList prevLocalContextStack;
final CallableInvocationContext prevMacroContext;
CallableInvocationContext(UnboundCallable callableDefinition,
Environment env,
TemplateElement nestedContent,
List nestedContentParameterNames) {
this.callableDefinition = callableDefinition;
this.localVars = env.new Namespace();
this.nestedContent = nestedContent;
this.nestedContentNamespace = env.getCurrentNamespace();
this.nestedContentTemplate = env.getCurrentTemplate();
this.nestedContentParameterNames = nestedContentParameterNames;
this.prevLocalContextStack = env.getLocalContextStack();
this.prevMacroContext = env.getCurrentMacroContext();
}
Macro getCallableDefinition() {
return callableDefinition;
}
void invoce(Environment env) throws TemplateException, IOException {
sanityCheck(env);
// Set default values for unspecified parameters
if (callableDefinition.getNestedBlock() != null) {
env.visit(callableDefinition.getNestedBlock());
}
}
// Set default parameters, check if all the required parameters are defined.
void sanityCheck(Environment env) throws TemplateException {
boolean resolvedAnArg, hasUnresolvedArg;
Expression firstUnresolvedExpression;
InvalidReferenceException firstReferenceException;
do {
firstUnresolvedExpression = null;
firstReferenceException = null;
resolvedAnArg = hasUnresolvedArg = false;
for (int i = 0; i < callableDefinition.getParamNames().length; ++i) {
String argName = callableDefinition.getParamNames()[i];
if (localVars.get(argName) == null) {
Expression valueExp = (Expression) callableDefinition.getParamDefaults().get(argName);
if (valueExp != null) {
try {
TemplateModel tm = valueExp.eval(env);
if (tm == null) {
if (!hasUnresolvedArg) {
firstUnresolvedExpression = valueExp;
hasUnresolvedArg = true;
}
} else {
localVars.put(argName, tm);
resolvedAnArg = true;
}
} catch (InvalidReferenceException e) {
if (!hasUnresolvedArg) {
hasUnresolvedArg = true;
firstReferenceException = e;
}
}
} else if (!env.isClassicCompatible()) {
boolean argWasSpecified = localVars.containsKey(argName);
throw new _MiscTemplateException(env,
new _ErrorDescriptionBuilder(new Object[] {
"When calling macro ", new _DelayedJQuote(callableDefinition.getName()),
", required parameter ", new _DelayedJQuote(argName),
" (parameter #", Integer.valueOf(i + 1), ") was ",
(argWasSpecified
? "specified, but had null/missing value."
: "not specified.")
}).tip(argWasSpecified
? new Object[] {
"If the parameter value expression on the caller side is known to "
+ "be legally null/missing, you may want to specify a default "
+ "value for it with the \"!\" operator, like "
+ "paramValue!defaultValue." }
: new Object[] {
"If the omission was deliberate, you may consider making the "
+ "parameter optional in the macro by specifying a default value "
+ "for it, like ", "<#macro macroName paramName=defaultExpr>", ")" }
));
}
}
}
} while (resolvedAnArg && hasUnresolvedArg);
if (hasUnresolvedArg) {
if (firstReferenceException != null) {
throw firstReferenceException;
} else if (!env.isClassicCompatible()) {
throw InvalidReferenceException.getInstance(firstUnresolvedExpression, env);
}
}
}
/**
* @return the local variable of the given name
* or null if it doesn't exist.
*/
public TemplateModel getLocalVariable(String name) throws TemplateModelException {
return localVars.get(name);
}
Environment.Namespace getLocals() {
return localVars;
}
/**
* Set a local variable in this macro
*/
void setLocalVar(String name, TemplateModel var) {
localVars.put(name, var);
}
public Collection getLocalVariableNames() throws TemplateModelException {
HashSet result = new HashSet();
for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) {
result.add(it.next().toString());
}
return result;
}
}