Forward ported from 2.3-gae: FREEMARKER-86: Added new built-ins: sequence?min and sequence?max (FREEMARKER-86), which return the smallest and greatest item from a list of numbers or date/time/date-times.
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/f2249459 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/f2249459 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/f2249459 Branch: refs/heads/3 Commit: f22494599393a6ff49b5ccb34f88beb474cbf55f Parents: 7f40cbe Author: ddekany <ddek...@apache.org> Authored: Mon Mar 12 07:04:00 2018 +0100 Committer: ddekany <ddek...@apache.org> Committed: Mon Mar 12 07:04:00 2018 +0100 ---------------------------------------------------------------------- .../apache/freemarker/core/MinMaxBITest.java | 89 ++++++++++++++++++++ .../apache/freemarker/core/ASTExpBuiltIn.java | 4 +- .../freemarker/core/BuiltInForIterable.java | 4 +- .../freemarker/core/BuiltInsForSequences.java | 52 ++++++++++-- 4 files changed, 141 insertions(+), 8 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f2249459/freemarker-core-test/src/test/java/org/apache/freemarker/core/MinMaxBITest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/MinMaxBITest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MinMaxBITest.java new file mode 100644 index 0000000..8a681d2 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/MinMaxBITest.java @@ -0,0 +1,89 @@ +/* + * 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.sql.Time; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.freemarker.core.model.ObjectWrapperWithAPISupport; +import org.apache.freemarker.core.model.impl.DefaultIterableAdapter; +import org.apache.freemarker.core.util._DateUtils; +import org.apache.freemarker.test.TemplateTest; +import org.apache.freemarker.test.TestConfigurationBuilder; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class MinMaxBITest extends TemplateTest { + + @Override + protected Configuration createDefaultConfiguration() throws Exception { + return new TestConfigurationBuilder().sqlDateAndTimeTimeZone(_DateUtils.UTC).timeFormat("HH:mm:ss").build(); + } + + @Test + public void basicsTest() throws Exception { + ObjectWrapperWithAPISupport ow = (ObjectWrapperWithAPISupport) getConfiguration().getObjectWrapper(); + for (boolean exposeAsSeq : new boolean[] { true, false }) { // Expose xs as SequenceTM or as CollectionTM + for (InputMinMax testParams : ImmutableList.of( + // Test parameters: List (xs), Expected result for `?min`, For `?max` + new InputMinMax(ImmutableList.of(1, 2, 3), "1", "3"), + new InputMinMax(ImmutableList.of(3, 2, 1), "1", "3"), + new InputMinMax(ImmutableList.of(1, 3, 2), "1", "3"), + new InputMinMax(ImmutableList.of(2, 1, 3), "1", "3"), + new InputMinMax(ImmutableList.of(2), "2", "2"), + new InputMinMax(Collections.emptyList(), "-", "-"), + new InputMinMax(ImmutableList.of(1.5, -0.5, 1L, 2.25), "-0.5", "2.25"), + new InputMinMax(ImmutableList.of(Double.NEGATIVE_INFINITY, 1, Double.POSITIVE_INFINITY), + "-\u221E", "\u221E"), // \u221E = â + new InputMinMax(Arrays.asList(new Object[] { null, 1, null, 2, null }), "1", "2"), + new InputMinMax(Arrays.asList(new Object[] { null, null, null }), "-", "-"), + new InputMinMax(ImmutableList.of(new Time(2000), new Time(3000), new Time(1000)), + "00:00:01", "00:00:03") + )) { + addToDataModel("xs", + exposeAsSeq ? testParams.input : DefaultIterableAdapter.adapt(testParams.input, ow)); + assertOutput("${xs?min!'-'}", testParams.minExpected); + assertOutput("${xs?max!'-'}", testParams.maxExpected); + } + } + } + + private class InputMinMax { + private final List<?> input; + private final String minExpected; + private final String maxExpected; + + public InputMinMax(List<?> input, String minExpected, String maxExpected) { + this.input = input; + this.minExpected = minExpected; + this.maxExpected = maxExpected; + } + } + + @Test + public void comparisonErrorTest() { + assertErrorContains("${['a', 'x']?min}", "less-than", "string"); + assertErrorContains("${[0, true]?min}", "number", "boolean"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f2249459/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 37163da..3ff5c0a 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 @@ -75,7 +75,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { protected ASTExpression target; protected String key; - static final int NUMBER_OF_BIS = 263; + static final int NUMBER_OF_BIS = 265; static final HashMap<String, ASTExpBuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f); static { @@ -229,6 +229,8 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { putBI("nodeNamespace", new node_namespaceBI()); putBI("nodeType", new node_typeBI()); putBI("noEsc", new no_escBI()); + putBI("max", new BuiltInsForSequences.maxBI()); + putBI("min", new BuiltInsForSequences.minBI()); putBI("number", new BuiltInsForStringsMisc.numberBI()); putBI("numberToDate", new number_to_dateBI(TemplateDateModel.DATE)); putBI("numberToTime", new number_to_dateBI(TemplateDateModel.TIME)); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f2249459/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java index 15556f8..ac3a56a 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java @@ -35,9 +35,9 @@ abstract class BuiltInForIterable extends ASTExpBuiltIn { TemplateIterableModel.class, null, env); } - return calculateResult((TemplateIterableModel) model); + return calculateResult((TemplateIterableModel) model, env); } - abstract TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException; + abstract TemplateModel calculateResult(TemplateIterableModel model, Environment env) throws TemplateException; } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f2249459/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java index ad50975..5df62f1 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java @@ -168,7 +168,7 @@ class BuiltInsForSequences { static class firstBI extends BuiltInForIterable { @Override - TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException { + TemplateModel calculateResult(TemplateIterableModel model, Environment env) throws TemplateException { TemplateModelIterator iter = model.iterator(); if (!iter.hasNext()) { return null; @@ -237,7 +237,7 @@ class BuiltInsForSequences { } @Override - TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException { + TemplateModel calculateResult(TemplateIterableModel model, Environment env) throws TemplateException { if (model instanceof RightUnboundedRangeModel) { throw new TemplateException( "The sequence to join was right-unbounded numerical range, thus it's infinitely long."); @@ -329,7 +329,7 @@ class BuiltInsForSequences { } @Override - TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException { + TemplateModel calculateResult(TemplateIterableModel model, Environment env) throws TemplateException { return new BIMethod(model); } @@ -473,7 +473,7 @@ class BuiltInsForSequences { } @Override - TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException { + TemplateModel calculateResult(TemplateIterableModel model, Environment env) throws TemplateException { return new BIMethod(model); } } @@ -819,7 +819,7 @@ class BuiltInsForSequences { static class sequenceBI extends BuiltInForIterable { @Override - TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException { + TemplateModel calculateResult(TemplateIterableModel model, Environment env) throws TemplateException { if (model instanceof TemplateSequenceModel) { return model; } @@ -835,6 +835,48 @@ class BuiltInsForSequences { } + private static abstract class MinOrMaxBI extends BuiltInForIterable { + + private final int comparatorOperator; + + protected MinOrMaxBI(int comparatorOperator) { + this.comparatorOperator = comparatorOperator; + } + + @Override + TemplateModel calculateResult(TemplateIterableModel model, Environment env) throws TemplateException { + // TODO Auto-generated method stub + TemplateModel best = null; + TemplateModelIterator iter = model.iterator(); + while (iter.hasNext()) { + TemplateModel cur = iter.next(); + if (cur != null + && (best == null || _EvalUtils.compare(cur, null, comparatorOperator, null, best, + null, this, true, false, false, false, env))) { + best = cur; + } + } + return best; + } + + } + + static class maxBI extends MinOrMaxBI { + + public maxBI() { + super(_EvalUtils.CMP_OP_GREATER_THAN); + } + + } + + static class minBI extends MinOrMaxBI { + + public minBI() { + super(_EvalUtils.CMP_OP_LESS_THAN); + } + + } + // Can't be instantiated private BuiltInsForSequences() { }