blob: d70d3560abc45ced769a69bf705b110a09535f01 [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.util.Date;
import java.util.List;
import freemarker.ext.beans.BeanModel;
import freemarker.ext.beans.OverloadedMethodsModel;
import freemarker.ext.beans.SimpleMethodModel;
import freemarker.ext.beans._BeansAPI;
import freemarker.template.SimpleDate;
import freemarker.template.SimpleNumber;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateCollectionModelEx;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelWithAPISupport;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.TemplateTransformModel;
import freemarker.template._TemplateAPI;
/**
* A holder for builtins that didn't fit into any other category.
*/
class BuiltInsForMultipleTypes {
static class cBI extends AbstractCBI implements ICIChainMember {
static class BIBeforeICE2d3d21 extends AbstractCBI {
@Override
protected TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateModelException {
Number num = EvalUtil.modelToNumber((TemplateNumberModel) model, target);
if (num instanceof Integer || num instanceof Long) {
// Accelerate these fairly common cases
return new SimpleScalar(num.toString());
} else {
return new SimpleScalar(env.getCNumberFormat().format(num));
}
}
}
private final BIBeforeICE2d3d21 prevICIObj = new BIBeforeICE2d3d21();
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel model = target.eval(env);
if (model instanceof TemplateNumberModel) {
return formatNumber(env, model);
} else if (model instanceof TemplateBooleanModel) {
return new SimpleScalar(((TemplateBooleanModel) model).getAsBoolean()
? MiscUtil.C_TRUE : MiscUtil.C_FALSE);
} else {
throw new UnexpectedTypeException(
target, model,
"number or boolean", new Class[] { TemplateNumberModel.class, TemplateBooleanModel.class },
env);
}
}
@Override
protected TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateModelException {
Number num = EvalUtil.modelToNumber((TemplateNumberModel) model, target);
if (num instanceof Integer || num instanceof Long) {
// Accelerate these fairly common cases
return new SimpleScalar(num.toString());
} else if (num instanceof Double) {
double n = num.doubleValue();
if (n == Double.POSITIVE_INFINITY) {
return new SimpleScalar("INF");
}
if (n == Double.NEGATIVE_INFINITY) {
return new SimpleScalar("-INF");
}
if (Double.isNaN(n)) {
return new SimpleScalar("NaN");
}
// Deliberately falls through
} else if (num instanceof Float) {
float n = num.floatValue();
if (n == Float.POSITIVE_INFINITY) {
return new SimpleScalar("INF");
}
if (n == Float.NEGATIVE_INFINITY) {
return new SimpleScalar("-INF");
}
if (Float.isNaN(n)) {
return new SimpleScalar("NaN");
}
// Deliberately falls through
}
return new SimpleScalar(env.getCNumberFormat().format(num));
}
public int getMinimumICIVersion() {
return _TemplateAPI.VERSION_INT_2_3_21;
}
public Object getPreviousICIChainMember() {
return prevICIObj;
}
}
static class dateBI extends BuiltIn {
private class DateParser
implements
TemplateDateModel,
TemplateMethodModel,
TemplateHashModel {
private final String text;
private final Environment env;
private final TemplateDateFormat defaultFormat;
private TemplateDateModel cachedValue;
DateParser(String text, Environment env)
throws TemplateException {
this.text = text;
this.env = env;
this.defaultFormat = env.getTemplateDateFormat(dateType, Date.class, target, false);
}
public Object exec(List args) throws TemplateModelException {
checkMethodArgCount(args, 0, 1);
return args.size() == 0 ? getAsDateModel() : get((String) args.get(0));
}
public TemplateModel get(String pattern) throws TemplateModelException {
TemplateDateFormat format;
try {
format = env.getTemplateDateFormat(pattern, dateType, Date.class, target, dateBI.this, true);
} catch (TemplateException e) {
// `e` should always be a TemplateModelException here, but to be sure:
throw _CoreAPI.ensureIsTemplateModelException("Failed to get format", e);
}
return toTemplateDateModel(parse(format));
}
private TemplateDateModel toTemplateDateModel(Object date) throws _TemplateModelException {
if (date instanceof Date) {
return new SimpleDate((Date) date, dateType);
} else {
TemplateDateModel tm = (TemplateDateModel) date;
if (tm.getDateType() != dateType) {
throw new _TemplateModelException("The result of the parsing was of the wrong date type.");
}
return tm;
}
}
private TemplateDateModel getAsDateModel() throws TemplateModelException {
if (cachedValue == null) {
cachedValue = toTemplateDateModel(parse(defaultFormat));
}
return cachedValue;
}
public Date getAsDate() throws TemplateModelException {
return getAsDateModel().getAsDate();
}
public int getDateType() {
return dateType;
}
public boolean isEmpty() {
return false;
}
private Object parse(TemplateDateFormat df)
throws TemplateModelException {
try {
return df.parse(text, dateType);
} catch (TemplateValueFormatException e) {
throw new _TemplateModelException(e,
"The string doesn't match the expected date/time/date-time format. "
+ "The string to parse was: ", new _DelayedJQuote(text), ". ",
"The expected format was: ", new _DelayedJQuote(df.getDescription()), ".",
e.getMessage() != null ? "\nThe nested reason given follows:\n" : "",
e.getMessage() != null ? e.getMessage() : "");
}
}
}
private final int dateType;
dateBI(int dateType) {
this.dateType = dateType;
}
@Override
TemplateModel _eval(Environment env)
throws TemplateException {
TemplateModel model = target.eval(env);
if (model instanceof TemplateDateModel) {
TemplateDateModel dmodel = (TemplateDateModel) model;
int dtype = dmodel.getDateType();
// Any date model can be coerced into its own type
if (dateType == dtype) {
return model;
}
// unknown and datetime can be coerced into any date type
if (dtype == TemplateDateModel.UNKNOWN || dtype == TemplateDateModel.DATETIME) {
return new SimpleDate(dmodel.getAsDate(), dateType);
}
throw new _MiscTemplateException(this,
"Cannot convert ", TemplateDateModel.TYPE_NAMES.get(dtype),
" to ", TemplateDateModel.TYPE_NAMES.get(dateType));
}
// Otherwise, interpret as a string and attempt
// to parse it into a date.
String s = target.evalAndCoerceToPlainText(env);
return new DateParser(s, env);
}
}
static class apiBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
if (!env.isAPIBuiltinEnabled()) {
throw new _MiscTemplateException(this,
"Can't use ?api, because the \"", Configurable.API_BUILTIN_ENABLED_KEY,
"\" configuration setting is false. Think twice before you set it to true though. Especially, "
+ "it shouldn't abussed for modifying Map-s and Collection-s.");
}
final TemplateModel tm = target.eval(env);
if (!(tm instanceof TemplateModelWithAPISupport)) {
target.assertNonNull(tm, env);
throw new APINotSupportedTemplateException(env, target, tm);
}
return ((TemplateModelWithAPISupport) tm).getAPI();
}
}
static class has_apiBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
final TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return tm instanceof TemplateModelWithAPISupport ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_booleanBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateBooleanModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_collectionBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateCollectionModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_collection_exBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateCollectionModelEx) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_dateLikeBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateDateModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_dateOfTypeBI extends BuiltIn {
private final int dateType;
is_dateOfTypeBI(int dateType) {
this.dateType = dateType;
}
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateDateModel) && ((TemplateDateModel) tm).getDateType() == dateType
? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_directiveBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
// [2.4] WRONG: it also had to check Macro.isFunction()
return (tm instanceof TemplateTransformModel || tm instanceof BoundCallable
|| tm instanceof TemplateDirectiveModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_enumerableBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)
&& (_TemplateAPI.getTemplateLanguageVersionAsInt(this) < _TemplateAPI.VERSION_INT_2_3_21
// These implement TemplateSequenceModel, yet they can't be #list-ed:
|| !(tm instanceof SimpleMethodModel || tm instanceof OverloadedMethodsModel))
? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_hash_exBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateHashModelEx) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_hashBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateHashModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_indexableBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateSequenceModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_macroBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
// WRONG: it also had to check Macro.isFunction()
return (tm instanceof BoundCallable) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_markup_outputBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateMarkupOutputModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_methodBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateMethodModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_nodeBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateNodeModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_numberBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateNumberModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_sequenceBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateSequenceModel
&& (
!(tm instanceof OverloadedMethodsModel || tm instanceof SimpleMethodModel)
|| !env.isIcI2324OrLater())
)
? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_stringBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
target.assertNonNull(tm, env);
return (tm instanceof TemplateScalarModel) ?
TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
}
}
static class is_transformBI extends BuiltIn {
@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 BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel tm = target.eval(env);
if (!(tm instanceof BoundCallable)) {
throw new UnexpectedTypeException(
target, tm,
"macro or function", new Class[] { BoundCallable.class },
env);
} else {
return ((BoundCallable) tm).getNamespace();
}
}
}
static class sizeBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel model = target.eval(env);
final int size;
if (model instanceof TemplateSequenceModel) {
size = ((TemplateSequenceModel) model).size();
} else if (model instanceof TemplateCollectionModelEx) {
size = ((TemplateCollectionModelEx) model).size();
} else if (model instanceof TemplateHashModelEx) {
size = ((TemplateHashModelEx) model).size();
} else {
throw new UnexpectedTypeException(
target, model,
"extended-hash or sequence or extended collection",
new Class[] {
TemplateHashModelEx.class,
TemplateSequenceModel.class,
TemplateCollectionModelEx.class
},
env);
}
return new SimpleNumber(size);
}
}
static class stringBI extends BuiltIn {
private class BooleanFormatter
implements
TemplateScalarModel,
TemplateMethodModel {
private final TemplateBooleanModel bool;
private final Environment env;
BooleanFormatter(TemplateBooleanModel bool, Environment env) {
this.bool = bool;
this.env = env;
}
public Object exec(List args) throws TemplateModelException {
checkMethodArgCount(args, 2);
return new SimpleScalar((String) args.get(bool.getAsBoolean() ? 0 : 1));
}
public String getAsString() throws TemplateModelException {
// Boolean should have come first... but that change would be non-BC.
if (bool instanceof TemplateScalarModel) {
return ((TemplateScalarModel) bool).getAsString();
} else {
try {
return env.formatBoolean(bool.getAsBoolean(), true);
} catch (TemplateException e) {
throw new TemplateModelException(e);
}
}
}
}
private class DateFormatter
implements
TemplateScalarModel,
TemplateHashModel,
TemplateMethodModel {
private final TemplateDateModel dateModel;
private final Environment env;
private final TemplateDateFormat defaultFormat;
private String cachedValue;
DateFormatter(TemplateDateModel dateModel, Environment env)
throws TemplateException {
this.dateModel = dateModel;
this.env = env;
final int dateType = dateModel.getDateType();
this.defaultFormat = dateType == TemplateDateModel.UNKNOWN
? null // Lazy unknown type error in getAsString()
: env.getTemplateDateFormat(
dateType, EvalUtil.modelToDate(dateModel, target).getClass(), target, true);
}
public Object exec(List args) throws TemplateModelException {
checkMethodArgCount(args, 1);
return formatWith((String) args.get(0));
}
public TemplateModel get(String key)
throws TemplateModelException {
return formatWith(key);
}
private TemplateModel formatWith(String key)
throws TemplateModelException {
try {
return new SimpleScalar(env.formatDateToPlainText(dateModel, key, target, stringBI.this, true));
} catch (TemplateException e) {
// `e` should always be a TemplateModelException here, but to be sure:
throw _CoreAPI.ensureIsTemplateModelException("Failed to format value", e);
}
}
public String getAsString()
throws TemplateModelException {
if (cachedValue == null) {
if (defaultFormat == null) {
if (dateModel.getDateType() == TemplateDateModel.UNKNOWN) {
throw MessageUtil.newCantFormatUnknownTypeDateException(target, null);
} else {
throw new BugException();
}
}
try {
cachedValue = EvalUtil.assertFormatResultNotNull(defaultFormat.formatToPlainText(dateModel));
} catch (TemplateValueFormatException e) {
try {
throw MessageUtil.newCantFormatDateException(defaultFormat, target, e, true);
} catch (TemplateException e2) {
// `e` should always be a TemplateModelException here, but to be sure:
throw _CoreAPI.ensureIsTemplateModelException("Failed to format date/time/datetime", e2);
}
}
}
return cachedValue;
}
public boolean isEmpty() {
return false;
}
}
private class NumberFormatter
implements
TemplateScalarModel,
TemplateHashModel,
TemplateMethodModel {
private final TemplateNumberModel numberModel;
private final Number number;
private final Environment env;
private final TemplateNumberFormat defaultFormat;
private String cachedValue;
NumberFormatter(TemplateNumberModel numberModel, Environment env) throws TemplateException {
this.env = env;
// As we format lazily, we need a snapshot of the format inputs:
this.numberModel = numberModel;
number = EvalUtil.modelToNumber(numberModel, target); // for BackwardCompatibleTemplateNumberFormat-s
try {
defaultFormat = env.getTemplateNumberFormat(stringBI.this, true);
} catch (TemplateException e) {
// `e` should always be a TemplateModelException here, but to be sure:
throw _CoreAPI.ensureIsTemplateModelException("Failed to get default number format", e);
}
}
public Object exec(List args) throws TemplateModelException {
checkMethodArgCount(args, 1);
return get((String) args.get(0));
}
public TemplateModel get(String key) throws TemplateModelException {
TemplateNumberFormat format;
try {
format = env.getTemplateNumberFormat(key, stringBI.this, true);
} catch (TemplateException e) {
// `e` should always be a TemplateModelException here, but to be sure:
throw _CoreAPI.ensureIsTemplateModelException("Failed to get number format", e);
}
String result;
try {
if (format instanceof BackwardCompatibleTemplateNumberFormat) {
result = env.formatNumberToPlainText(number, (BackwardCompatibleTemplateNumberFormat) format, target);
} else {
result = env.formatNumberToPlainText(numberModel, format, target, true);
}
} catch (TemplateException e) {
// `e` should always be a TemplateModelException here, but to be sure:
throw _CoreAPI.ensureIsTemplateModelException("Failed to format number", e);
}
return new SimpleScalar(result);
}
public String getAsString() throws TemplateModelException {
if (cachedValue == null) {
try {
if (defaultFormat instanceof BackwardCompatibleTemplateNumberFormat) {
cachedValue = env.formatNumberToPlainText(
number, (BackwardCompatibleTemplateNumberFormat) defaultFormat, target);
} else {
cachedValue = env.formatNumberToPlainText(numberModel, defaultFormat, target, true);
}
} catch (TemplateException e) {
// `e` should always be a TemplateModelException here, but to be sure:
throw _CoreAPI.ensureIsTemplateModelException("Failed to format number", e);
}
}
return cachedValue;
}
public boolean isEmpty() {
return false;
}
}
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel model = target.eval(env);
if (model instanceof TemplateNumberModel) {
TemplateNumberModel numberModel = (TemplateNumberModel) model;
Number num = EvalUtil.modelToNumber(numberModel, target);
return new NumberFormatter(numberModel, env);
} else if (model instanceof TemplateDateModel) {
TemplateDateModel dm = (TemplateDateModel) model;
return new DateFormatter(dm, env);
} else if (model instanceof SimpleScalar) {
return model;
} else if (model instanceof TemplateBooleanModel) {
return new BooleanFormatter((TemplateBooleanModel) model, env);
} else if (model instanceof TemplateScalarModel) {
return new SimpleScalar(((TemplateScalarModel) model).getAsString());
} else if (env.isClassicCompatible() && model instanceof BeanModel) {
return new SimpleScalar(_BeansAPI.getAsClassicCompatibleString((BeanModel) model));
} else {
throw new UnexpectedTypeException(
target, model,
"number, date, boolean or string",
new Class[] {
TemplateNumberModel.class, TemplateDateModel.class, TemplateBooleanModel.class,
TemplateScalarModel.class
},
env);
}
}
}
// Can't be instantiated
private BuiltInsForMultipleTypes() { }
static abstract class AbstractCBI extends BuiltIn {
@Override
TemplateModel _eval(Environment env) throws TemplateException {
TemplateModel model = target.eval(env);
if (model instanceof TemplateNumberModel) {
return formatNumber(env, model);
} else if (model instanceof TemplateBooleanModel) {
return new SimpleScalar(((TemplateBooleanModel) model).getAsBoolean()
? MiscUtil.C_TRUE : MiscUtil.C_FALSE);
} else {
throw new UnexpectedTypeException(
target, model,
"number or boolean", new Class[] { TemplateNumberModel.class, TemplateBooleanModel.class },
env);
}
}
protected abstract TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateModelException;
}
}