You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2017/05/14 10:53:25 UTC
[42/51] [partial] incubator-freemarker git commit: Migrated from Ant
to Gradle, and modularized the project. This is an incomplete migration;
there are some TODO-s in the build scripts, and release related tasks are
still missing. What works: Building th
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/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
new file mode 100644
index 0000000..ab3df64
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -0,0 +1,717 @@
+/*
+ * 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.Date;
+import java.util.List;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateCollectionModelEx;
+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.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
+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;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+
+/**
+ * A holder for builtins that didn't fit into any other category.
+ */
+class BuiltInsForMultipleTypes {
+
+ static class cBI extends AbstractCBI {
+
+ @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));
+ }
+
+ }
+
+ static class dateBI extends ASTExpBuiltIn {
+ 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;
+ defaultFormat = env.getTemplateDateFormat(dateType, Date.class, target, false);
+ }
+
+ @Override
+ public Object exec(List args) throws TemplateModelException {
+ checkMethodArgCount(args, 0, 1);
+ return args.size() == 0 ? getAsDateModel() : get((String) args.get(0));
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ public Date getAsDate() throws TemplateModelException {
+ return getAsDateModel().getAsDate();
+ }
+
+ @Override
+ public int getDateType() {
+ return dateType;
+ }
+
+ @Override
+ 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 ASTExpBuiltIn {
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ if (!env.getAPIBuiltinEnabled()) {
+ throw new _MiscTemplateException(this,
+ "Can't use ?api, because the \"", MutableProcessingConfiguration.API_BUILTIN_ENABLED_KEY,
+ "\" configuration setting is false. Think twice before you set it to true though. Especially, "
+ + "it shouldn't abused 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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+
+ 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 ASTExpBuiltIn {
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ 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) ?
+ TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+ }
+ }
+
+ static class is_enumerableBI extends ASTExpBuiltIn {
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ TemplateModel tm = target.eval(env);
+ target.assertNonNull(tm, env);
+ return (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)
+ ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+ }
+ }
+
+ static class is_hash_exBI extends ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ TemplateModel tm = target.eval(env);
+ target.assertNonNull(tm, env);
+ // WRONG: it also had to check ASTDirMacro.isFunction()
+ return (tm instanceof ASTDirMacro) ?
+ TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+ }
+ }
+
+ static class is_markup_outputBI extends ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+ @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_stringBI extends ASTExpBuiltIn {
+ @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 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 {
+ TemplateModel tm = target.eval(env);
+ if (!(tm instanceof ASTDirMacro)) {
+ throw new UnexpectedTypeException(
+ target, tm,
+ "macro or function", new Class[] { ASTDirMacro.class },
+ env);
+ } else {
+ return env.getMacroNamespace((ASTDirMacro) tm);
+ }
+ }
+ }
+
+ static class sizeBI extends ASTExpBuiltIn {
+ @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 ASTExpBuiltIn {
+
+ 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;
+ }
+
+ @Override
+ public Object exec(List args) throws TemplateModelException {
+ checkMethodArgCount(args, 2);
+ return new SimpleScalar((String) args.get(bool.getAsBoolean() ? 0 : 1));
+ }
+
+ @Override
+ 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();
+ defaultFormat = dateType == TemplateDateModel.UNKNOWN
+ ? null // Lazy unknown type error in getAsString()
+ : env.getTemplateDateFormat(
+ dateType, _EvalUtil.modelToDate(dateModel, target).getClass(), target, true);
+ }
+
+ @Override
+ public Object exec(List args) throws TemplateModelException {
+ checkMethodArgCount(args, 1);
+ return formatWith((String) args.get(0));
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ 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;
+ }
+
+ @Override
+ 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);
+ }
+ }
+
+ @Override
+ public Object exec(List args) throws TemplateModelException {
+ checkMethodArgCount(args, 1);
+ return get((String) args.get(0));
+ }
+
+ @Override
+ 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 {
+ 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);
+ }
+
+ @Override
+ public String getAsString() throws TemplateModelException {
+ if (cachedValue == null) {
+ try {
+ 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;
+ }
+
+ @Override
+ 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 {
+ 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 ASTExpBuiltIn {
+
+ @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;
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java
new file mode 100644
index 0000000..39bc546
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java
@@ -0,0 +1,154 @@
+/*
+ * 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.List;
+
+import org.apache.freemarker.core.model.TemplateMethodModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNodeModel;
+import org.apache.freemarker.core.model.TemplateNodeModelEx;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * A holder for builtins that operate exclusively on (XML-)node left-hand value.
+ */
+class BuiltInsForNodes {
+
+ static class ancestorsBI extends BuiltInForNode {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) throws TemplateModelException {
+ AncestorSequence result = new AncestorSequence(env);
+ TemplateNodeModel parent = nodeModel.getParentNode();
+ while (parent != null) {
+ result.add(parent);
+ parent = parent.getParentNode();
+ }
+ return result;
+ }
+ }
+
+ static class childrenBI extends BuiltInForNode {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) throws TemplateModelException {
+ return nodeModel.getChildNodes();
+ }
+ }
+
+ static class node_nameBI extends BuiltInForNode {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) throws TemplateModelException {
+ return new SimpleScalar(nodeModel.getNodeName());
+ }
+ }
+
+
+ static class node_namespaceBI extends BuiltInForNode {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) throws TemplateModelException {
+ String nsURI = nodeModel.getNodeNamespace();
+ return nsURI == null ? null : new SimpleScalar(nsURI);
+ }
+ }
+
+ static class node_typeBI extends BuiltInForNode {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) throws TemplateModelException {
+ return new SimpleScalar(nodeModel.getNodeType());
+ }
+ }
+
+ static class parentBI extends BuiltInForNode {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) throws TemplateModelException {
+ return nodeModel.getParentNode();
+ }
+ }
+
+ static class rootBI extends BuiltInForNode {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModel nodeModel, Environment env) throws TemplateModelException {
+ TemplateNodeModel result = nodeModel;
+ TemplateNodeModel parent = nodeModel.getParentNode();
+ while (parent != null) {
+ result = parent;
+ parent = result.getParentNode();
+ }
+ return result;
+ }
+ }
+
+ static class previousSiblingBI extends BuiltInForNodeEx {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModelEx nodeModel, Environment env) throws TemplateModelException {
+ return nodeModel.getPreviousSibling();
+ }
+ }
+
+ static class nextSiblingBI extends BuiltInForNodeEx {
+ @Override
+ TemplateModel calculateResult(TemplateNodeModelEx nodeModel, Environment env) throws TemplateModelException {
+ return nodeModel.getNextSibling();
+ }
+ }
+
+ // Can't be instantiated
+ private BuiltInsForNodes() { }
+
+ static class AncestorSequence extends NativeSequence implements TemplateMethodModel {
+
+ private static final int INITIAL_CAPACITY = 12;
+
+ private Environment env;
+
+ AncestorSequence(Environment env) {
+ super(INITIAL_CAPACITY);
+ this.env = env;
+ }
+
+ @Override
+ public Object exec(List names) throws TemplateModelException {
+ if (names == null || names.isEmpty()) {
+ return this;
+ }
+ AncestorSequence result = new AncestorSequence(env);
+ for (int i = 0; i < size(); i++) {
+ TemplateNodeModel tnm = (TemplateNodeModel) get(i);
+ String nodeName = tnm.getNodeName();
+ String nsURI = tnm.getNodeNamespace();
+ if (nsURI == null) {
+ if (names.contains(nodeName)) {
+ result.add(tnm);
+ }
+ } else {
+ for (int j = 0; j < names.size(); j++) {
+ if (_StringUtil.matchesQName((String) names.get(j), nodeName, nsURI, env)) {
+ result.add(tnm);
+ break;
+ }
+ }
+ }
+ }
+ return result;
+ }
+ }
+}
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNumbers.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNumbers.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNumbers.java
new file mode 100644
index 0000000..58a2aa6
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNumbers.java
@@ -0,0 +1,319 @@
+/*
+ * 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.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Date;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.impl.SimpleDate;
+import org.apache.freemarker.core.model.impl.SimpleNumber;
+import org.apache.freemarker.core.model.impl.SimpleScalar;
+import org.apache.freemarker.core.util._NumberUtil;
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * A holder for builtins that operate exclusively on number left-hand value.
+ */
+class BuiltInsForNumbers {
+
+ private static abstract class abcBI extends BuiltInForNumber {
+
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) throws TemplateModelException {
+ final int n;
+ try {
+ n = _NumberUtil.toIntExact(num);
+ } catch (ArithmeticException e) {
+ throw new _TemplateModelException(target,
+ "The left side operand value isn't compatible with ?", key, ": ", e.getMessage());
+
+ }
+ if (n <= 0) {
+ throw new _TemplateModelException(target,
+ "The left side operand of to ?", key, " must be at least 1, but was ", Integer.valueOf(n), ".");
+ }
+ return new SimpleScalar(toABC(n));
+ }
+
+ protected abstract String toABC(int n);
+
+ }
+
+ static class lower_abcBI extends abcBI {
+
+ @Override
+ protected String toABC(int n) {
+ return _StringUtil.toLowerABC(n);
+ }
+
+ }
+
+ static class upper_abcBI extends abcBI {
+
+ @Override
+ protected String toABC(int n) {
+ return _StringUtil.toUpperABC(n);
+ }
+
+ }
+
+ static class absBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) throws TemplateModelException {
+ if (num instanceof Integer) {
+ int n = num.intValue();
+ if (n < 0) {
+ return new SimpleNumber(-n);
+ } else {
+ return model;
+ }
+ } else if (num instanceof BigDecimal) {
+ BigDecimal n = (BigDecimal) num;
+ if (n.signum() < 0) {
+ return new SimpleNumber(n.negate());
+ } else {
+ return model;
+ }
+ } else if (num instanceof Double) {
+ double n = num.doubleValue();
+ if (n < 0) {
+ return new SimpleNumber(-n);
+ } else {
+ return model;
+ }
+ } else if (num instanceof Float) {
+ float n = num.floatValue();
+ if (n < 0) {
+ return new SimpleNumber(-n);
+ } else {
+ return model;
+ }
+ } else if (num instanceof Long) {
+ long n = num.longValue();
+ if (n < 0) {
+ return new SimpleNumber(-n);
+ } else {
+ return model;
+ }
+ } else if (num instanceof Short) {
+ short n = num.shortValue();
+ if (n < 0) {
+ return new SimpleNumber(-n);
+ } else {
+ return model;
+ }
+ } else if (num instanceof Byte) {
+ byte n = num.byteValue();
+ if (n < 0) {
+ return new SimpleNumber(-n);
+ } else {
+ return model;
+ }
+ } else if (num instanceof BigInteger) {
+ BigInteger n = (BigInteger) num;
+ if (n.signum() < 0) {
+ return new SimpleNumber(n.negate());
+ } else {
+ return model;
+ }
+ } else {
+ throw new _TemplateModelException("Unsupported number class: ", num.getClass());
+ }
+ }
+ }
+
+ static class byteBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ if (num instanceof Byte) {
+ return model;
+ }
+ return new SimpleNumber(Byte.valueOf(num.byteValue()));
+ }
+ }
+
+ static class ceilingBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ return new SimpleNumber(new BigDecimal(num.doubleValue()).divide(BIG_DECIMAL_ONE, 0, BigDecimal.ROUND_CEILING));
+ }
+ }
+
+ static class doubleBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ if (num instanceof Double) {
+ return model;
+ }
+ return new SimpleNumber(num.doubleValue());
+ }
+ }
+
+ static class floatBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ if (num instanceof Float) {
+ return model;
+ }
+ return new SimpleNumber(num.floatValue());
+ }
+ }
+
+ static class floorBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ return new SimpleNumber(new BigDecimal(num.doubleValue()).divide(BIG_DECIMAL_ONE, 0, BigDecimal.ROUND_FLOOR));
+ }
+ }
+
+ static class intBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ if (num instanceof Integer) {
+ return model;
+ }
+ return new SimpleNumber(num.intValue());
+ }
+ }
+
+ static class is_infiniteBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) throws TemplateModelException {
+ return _NumberUtil.isInfinite(num) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+ }
+ }
+
+
+ static class is_nanBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) throws TemplateModelException {
+ return _NumberUtil.isNaN(num) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+ }
+ }
+
+ // Does both someNumber?long and someDate?long, thus it doesn't extend NumberBuiltIn
+ static class longBI extends ASTExpBuiltIn {
+ @Override
+ TemplateModel _eval(Environment env)
+ throws TemplateException {
+ TemplateModel model = target.eval(env);
+ if (!(model instanceof TemplateNumberModel)
+ && model instanceof TemplateDateModel) {
+ Date date = _EvalUtil.modelToDate((TemplateDateModel) model, target);
+ return new SimpleNumber(date.getTime());
+ } else {
+ Number num = target.modelToNumber(model, env);
+ if (num instanceof Long) {
+ return model;
+ }
+ return new SimpleNumber(num.longValue());
+ }
+ }
+ }
+
+ static class number_to_dateBI extends BuiltInForNumber {
+
+ private final int dateType;
+
+ number_to_dateBI(int dateType) {
+ this.dateType = dateType;
+ }
+
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model)
+ throws TemplateModelException {
+ return new SimpleDate(new Date(safeToLong(num)), dateType);
+ }
+ }
+
+ static class roundBI extends BuiltInForNumber {
+ private static final BigDecimal half = new BigDecimal("0.5");
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ return new SimpleNumber(new BigDecimal(num.doubleValue()).add(half).divide(BIG_DECIMAL_ONE, 0, BigDecimal.ROUND_FLOOR));
+ }
+ }
+
+ static class shortBI extends BuiltInForNumber {
+ @Override
+ TemplateModel calculateResult(Number num, TemplateModel model) {
+ if (num instanceof Short) {
+ return model;
+ }
+ return new SimpleNumber(Short.valueOf(num.shortValue()));
+ }
+ }
+
+ private static long safeToLong(Number num) throws TemplateModelException {
+ if (num instanceof Double) {
+ double d = Math.round(num.doubleValue());
+ if (d > Long.MAX_VALUE || d < Long.MIN_VALUE) {
+ throw new _TemplateModelException(
+ "Number doesn't fit into a 64 bit signed integer (long): ", Double.valueOf(d));
+ } else {
+ return (long) d;
+ }
+ } else if (num instanceof Float) {
+ float f = Math.round(num.floatValue());
+ if (f > Long.MAX_VALUE || f < Long.MIN_VALUE) {
+ throw new _TemplateModelException(
+ "Number doesn't fit into a 64 bit signed integer (long): ", Float.valueOf(f));
+ } else {
+ return (long) f;
+ }
+ } else if (num instanceof BigDecimal) {
+ BigDecimal bd = ((BigDecimal) num).setScale(0, BigDecimal.ROUND_HALF_UP);
+ if (bd.compareTo(BIG_DECIMAL_LONG_MAX) > 0 || bd.compareTo(BIG_DECIMAL_LONG_MIN) < 0) {
+ throw new _TemplateModelException("Number doesn't fit into a 64 bit signed integer (long): ", bd);
+ } else {
+ return bd.longValue();
+ }
+ } else if (num instanceof BigInteger) {
+ BigInteger bi = (BigInteger) num;
+ if (bi.compareTo(BIG_INTEGER_LONG_MAX) > 0 || bi.compareTo(BIG_INTEGER_LONG_MIN) < 0) {
+ throw new _TemplateModelException("Number doesn't fit into a 64 bit signed integer (long): ", bi);
+ } else {
+ return bi.longValue();
+ }
+ } else if (num instanceof Long || num instanceof Integer || num instanceof Byte || num instanceof Short) {
+ return num.longValue();
+ } else {
+ // Should add Atomic* types in 2.4...
+ throw new _TemplateModelException("Unsupported number type: ", num.getClass());
+ }
+ }
+
+ private static final BigDecimal BIG_DECIMAL_ONE = new BigDecimal("1");
+ private static final BigDecimal BIG_DECIMAL_LONG_MIN = BigDecimal.valueOf(Long.MIN_VALUE);
+ private static final BigDecimal BIG_DECIMAL_LONG_MAX = BigDecimal.valueOf(Long.MAX_VALUE);
+ private static final BigInteger BIG_INTEGER_LONG_MIN = BigInteger.valueOf(Long.MIN_VALUE);
+
+ private static final BigInteger BIG_INTEGER_LONG_MAX = BigInteger.valueOf(Long.MAX_VALUE);
+
+ // Can't be instantiated
+ private BuiltInsForNumbers() { }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForOutputFormatRelated.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForOutputFormatRelated.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForOutputFormatRelated.java
new file mode 100644
index 0000000..2ae2fe7
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForOutputFormatRelated.java
@@ -0,0 +1,84 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+
+class BuiltInsForOutputFormatRelated {
+
+ static class no_escBI extends AbstractConverterBI {
+
+ @Override
+ protected TemplateModel calculateResult(String lho, MarkupOutputFormat outputFormat, Environment env)
+ throws TemplateException {
+ return outputFormat.fromMarkup(lho);
+ }
+
+ }
+
+ static class escBI extends AbstractConverterBI {
+
+ @Override
+ protected TemplateModel calculateResult(String lho, MarkupOutputFormat outputFormat, Environment env)
+ throws TemplateException {
+ return outputFormat.fromPlainTextByEscaping(lho);
+ }
+
+ }
+
+ static abstract class AbstractConverterBI extends MarkupOutputFormatBoundBuiltIn {
+
+ @Override
+ protected TemplateModel calculateResult(Environment env) throws TemplateException {
+ TemplateModel lhoTM = target.eval(env);
+ Object lhoMOOrStr = _EvalUtil.coerceModelToStringOrMarkup(lhoTM, target, null, env);
+ MarkupOutputFormat contextOF = outputFormat;
+ if (lhoMOOrStr instanceof String) { // TemplateMarkupOutputModel
+ return calculateResult((String) lhoMOOrStr, contextOF, env);
+ } else {
+ TemplateMarkupOutputModel lhoMO = (TemplateMarkupOutputModel) lhoMOOrStr;
+ MarkupOutputFormat lhoOF = lhoMO.getOutputFormat();
+ // ATTENTION: Keep this logic in sync. with ${...}'s logic!
+ if (lhoOF == contextOF || contextOF.isOutputFormatMixingAllowed()) {
+ // bypass
+ return lhoMO;
+ } else {
+ // ATTENTION: Keep this logic in sync. with ${...}'s logic!
+ String lhoPlainTtext = lhoOF.getSourcePlainText(lhoMO);
+ if (lhoPlainTtext == null) {
+ throw new _TemplateModelException(target,
+ "The left side operand of ?", key, " is in ", new _DelayedToString(lhoOF),
+ " format, which differs from the current output format, ",
+ new _DelayedToString(contextOF), ". Conversion wasn't possible.");
+ }
+ // Here we know that lho is escaped plain text. So we re-escape it to the current format and
+ // bypass it, just as if the two output formats were the same earlier.
+ return contextOF.fromPlainTextByEscaping(lhoPlainTtext);
+ }
+ }
+ }
+
+ protected abstract TemplateModel calculateResult(String lho, MarkupOutputFormat outputFormat, Environment env)
+ throws TemplateException;
+
+ }
+
+}