blob: 87d66479368691473c3cec9bd729bc6aeb44e13b [file] [log] [blame]
/*
* 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 freemarker.core;
import java.io.IOException;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateHashModelEx2;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateMethodModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.utility.TemplateModelUtils;
class BuiltInsForCallables {
static abstract class AbstractWithArgsBI extends BuiltIn {
protected abstract boolean isOrderLast();
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel model = target.eval(env);
if (model instanceof Macro) {
return new BIMethodForMacroAndFunction((Macro) model);
} else if (model instanceof TemplateDirectiveModel) {
return new BIMethodForDirective((TemplateDirectiveModel) model);
} else if (model instanceof TemplateMethodModel) {
return new BIMethodForMethod((TemplateMethodModel) model);
} else {
throw new UnexpectedTypeException(
target, model,
"macro, function, directive, or method", new Class[] { Macro.class,
TemplateDirectiveModel.class, TemplateMethodModel.class },
env);
}
}
private class BIMethodForMacroAndFunction implements TemplateMethodModelEx {
private final Macro macroOrFunction;
private BIMethodForMacroAndFunction(Macro macroOrFunction) {
this.macroOrFunction = macroOrFunction;
}
public Object exec(List args) throws TemplateModelException {
checkMethodArgCount(args.size(), 1);
TemplateModel argTM = (TemplateModel) args.get(0);
Macro.WithArgs withArgs;
if (argTM instanceof TemplateSequenceModel) {
withArgs = new Macro.WithArgs((TemplateSequenceModel) argTM, isOrderLast());
} else if (argTM instanceof TemplateHashModelEx) {
if (macroOrFunction.isFunction()) {
throw new _TemplateModelException("When applied on a function, ?", key,
" can't have a hash argument. Use a sequence argument.");
}
withArgs = new Macro.WithArgs((TemplateHashModelEx) argTM, isOrderLast());
} else {
throw _MessageUtil.newMethodArgMustBeExtendedHashOrSequnceException("?" + key, 0, argTM);
}
return new Macro(macroOrFunction, withArgs);
}
}
private class BIMethodForMethod implements TemplateMethodModelEx {
private final TemplateMethodModel method;
public BIMethodForMethod(TemplateMethodModel method) {
this.method = method;
}
public Object exec(List args) throws TemplateModelException {
checkMethodArgCount(args.size(), 1);
TemplateModel argTM = (TemplateModel) args.get(0);
if (argTM instanceof TemplateSequenceModel) {
final TemplateSequenceModel withArgs = (TemplateSequenceModel) argTM;
if (method instanceof TemplateMethodModelEx) {
return new TemplateMethodModelEx() {
public Object exec(List origArgs) throws TemplateModelException {
int withArgsSize = withArgs.size();
List<TemplateModel> newArgs = new ArrayList<TemplateModel>(
withArgsSize + origArgs.size());
if (isOrderLast()) {
newArgs.addAll(origArgs);
}
for (int i = 0; i < withArgsSize; i++) {
newArgs.add(withArgs.get(i));
}
if (!isOrderLast()) {
newArgs.addAll(origArgs);
}
return method.exec(newArgs);
}
};
} else {
return new TemplateMethodModel() {
public Object exec(List origArgs) throws TemplateModelException {
int withArgsSize = withArgs.size();
List<String> newArgs = new ArrayList<String>(
withArgsSize + origArgs.size());
if (isOrderLast()) {
newArgs.addAll(origArgs);
}
for (int i = 0; i < withArgsSize; i++) {
TemplateModel argVal = withArgs.get(i);
newArgs.add(argValueToString(argVal));
}
if (!isOrderLast()) {
newArgs.addAll(origArgs);
}
return method.exec(newArgs);
}
/**
* Mimics the behavior of method call expression when it calls legacy method model.
*/
private String argValueToString(TemplateModel argVal) throws TemplateModelException {
String argValStr;
if (argVal instanceof TemplateScalarModel) {
argValStr = ((TemplateScalarModel) argVal).getAsString();
} else if (argVal == null) {
argValStr = null;
} else {
try {
argValStr = EvalUtil.coerceModelToPlainText(argVal, null, null,
Environment.getCurrentEnvironment());
} catch (TemplateException e) {
throw new _TemplateModelException(e,
"Failed to convert method argument to string. Argument type was: ",
new _DelayedFTLTypeDescription(argVal));
}
}
return argValStr;
}
};
}
} else if (argTM instanceof TemplateHashModelEx) {
throw new _TemplateModelException("When applied on a method, ?", key,
" can't have a hash argument. Use a sequence argument.");
} else {
throw _MessageUtil.newMethodArgMustBeExtendedHashOrSequnceException("?" + key, 0, argTM);
}
}
}
private class BIMethodForDirective implements TemplateMethodModelEx {
private final TemplateDirectiveModel directive;
public BIMethodForDirective(TemplateDirectiveModel directive) {
this.directive = directive;
}
public Object exec(List args) throws TemplateModelException {
checkMethodArgCount(args.size(), 1);
TemplateModel argTM = (TemplateModel) args.get(0);
if (argTM instanceof TemplateHashModelEx) {
final TemplateHashModelEx withArgs = (TemplateHashModelEx) argTM;
return new TemplateDirectiveModel() {
public void execute(Environment env, Map origArgs, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
int withArgsSize = withArgs.size();
// This is unnecessarily big if there are overridden arguments, but we care more about
// avoiding rehashing.
Map<String, TemplateModel> newArgs = new LinkedHashMap<String, TemplateModel>(
(withArgsSize + origArgs.size()) * 4 / 3, 1f);
TemplateHashModelEx2.KeyValuePairIterator withArgsIter =
TemplateModelUtils.getKeyValuePairIterator(withArgs);
if (isOrderLast()) {
newArgs.putAll(origArgs);
while (withArgsIter.hasNext()) {
TemplateHashModelEx2.KeyValuePair withArgsKVP = withArgsIter.next();
String argName = getArgumentName(withArgsKVP);
if (!newArgs.containsKey(argName)) {
newArgs.put(argName, withArgsKVP.getValue());
}
}
} else {
while (withArgsIter.hasNext()) {
TemplateHashModelEx2.KeyValuePair withArgsKVP = withArgsIter.next();
newArgs.put(getArgumentName(withArgsKVP), withArgsKVP.getValue());
}
newArgs.putAll(origArgs);
}
directive.execute(env, newArgs, loopVars, body);
}
private String getArgumentName(TemplateHashModelEx2.KeyValuePair withArgsKVP) throws
TemplateModelException {
TemplateModel argNameTM = withArgsKVP.getKey();
if (!(argNameTM instanceof TemplateScalarModel)) {
throw new _TemplateModelException(
"Expected string keys in the ?", key, "(...) arguments, " +
"but one of the keys was ",
new _DelayedAOrAn(new _DelayedFTLTypeDescription(argNameTM)), ".");
}
return EvalUtil.modelToString((TemplateScalarModel) argNameTM, null, null);
}
};
} else if (argTM instanceof TemplateSequenceModel) {
throw new _TemplateModelException("When applied on a directive, ?", key,
"(...) can't have a sequence argument. Use a hash argument.");
} else {
throw _MessageUtil.newMethodArgMustBeExtendedHashOrSequnceException("?" + key, 0, argTM);
}
}
}
}
static final class with_argsBI extends AbstractWithArgsBI {
@Override
protected boolean isOrderLast() {
return false;
}
}
static final class with_args_lastBI extends AbstractWithArgsBI {
@Override
protected boolean isOrderLast() {
return true;
}
}
}