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 2018/03/12 06:06:41 UTC

[1/5] incubator-freemarker git commit: (Fixed misplaced anchor in Manual)

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3 5bfa9ac42 -> 0c69b1983


(Fixed misplaced anchor in Manual)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/3d2530c0
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/3d2530c0
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/3d2530c0

Branch: refs/heads/2.3
Commit: 3d2530c0873629e14bf37d743884d0d35b03a9de
Parents: 8286a7b
Author: ddekany <dd...@apache.org>
Authored: Sat Mar 10 21:31:45 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sat Mar 10 21:31:45 2018 +0100

----------------------------------------------------------------------
 src/manual/en_US/book.xml | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3d2530c0/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 804cb15..e4cf87a 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -23274,7 +23274,7 @@ There was no specific handler for node y
         </listitem>
 
         <listitem>
-          <para><indexterm>
+          <para xml:id="ref_specvar_caller_template_name"><indexterm>
               <primary>caller_template_name</primary>
             </indexterm><literal>caller_template_name</literal> (available
           since FreeMarker 2.3.28): Returns the name (path) of the template
@@ -23296,7 +23296,7 @@ There was no specific handler for node y
           object.</phrase>)</para>
         </listitem>
 
-        <listitem xml:id="ref_specvar_caller_template_name">
+        <listitem>
           <para><indexterm>
               <primary>current_template_name</primary>
             </indexterm><literal>current_template_name</literal>: The name of


[2/5] incubator-freemarker git commit: The default arithmetic engine (ArithmeticEngine.BIGDECIMAL_ENGINE) can now compare infinite (both positive and negative) to any other standard type. Earlier, since BigDecimal can't represent infinite, it was only wo

Posted by dd...@apache.org.
The default arithmetic engine (ArithmeticEngine.BIGDECIMAL_ENGINE) can now compare infinite (both positive and negative) to any other standard type. Earlier, since BigDecimal can't represent infinite, it was only working in certain special cases. Also did some performance optimizations to slightly decrease the impact and number of conversions to BigDecimal.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/b996adaa
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/b996adaa
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/b996adaa

Branch: refs/heads/2.3
Commit: b996adaa2a574b269c2a83fd7a2a7522aea62e3c
Parents: 3d2530c
Author: ddekany <dd...@apache.org>
Authored: Sun Mar 11 22:23:08 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Sun Mar 11 22:23:08 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/core/ArithmeticEngine.java  | 110 +++++++++++++++++--
 .../freemarker/template/utility/NumberUtil.java |  13 ++-
 src/manual/en_US/book.xml                       |  11 ++
 .../core/BigDecimalArithmeticEngineTest.java    |  97 ++++++++++++++++
 4 files changed, 221 insertions(+), 10 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b996adaa/src/main/java/freemarker/core/ArithmeticEngine.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ArithmeticEngine.java b/src/main/java/freemarker/core/ArithmeticEngine.java
index 9eedf64..31648f9 100644
--- a/src/main/java/freemarker/core/ArithmeticEngine.java
+++ b/src/main/java/freemarker/core/ArithmeticEngine.java
@@ -111,9 +111,8 @@ public abstract class ArithmeticEngine {
      * number it receives into {@link BigDecimal}, then operates on these
      * converted {@link BigDecimal}s.
      */
-    public static class BigDecimalEngine
-    extends
-        ArithmeticEngine {
+    public static class BigDecimalEngine extends ArithmeticEngine {
+        
         @Override
         public int compareNumbers(Number first, Number second) {
             // We try to find the result based on the sign (+/-/0) first, because:
@@ -126,9 +125,83 @@ public abstract class ArithmeticEngine {
             } else if (firstSignum == 0 && secondSignum == 0) {
                 return 0;
             } else {
-                BigDecimal left = toBigDecimal(first);
-                BigDecimal right = toBigDecimal(second);
-                return left.compareTo(right);
+                // The most common case is comparing values of the same type. As BigDecimal can represent all of these
+                // with loseless round-trip (i.e., converting to BigDecimal and then back the original type gives the
+                // original value), we can avoid conversion to BigDecimal without changing the result.
+                if (first.getClass() == second.getClass()) {
+                    // Bit of optimization for this is a very common case:
+                    if (first instanceof BigDecimal) {
+                        return ((BigDecimal) first).compareTo((BigDecimal) second);
+                    }
+                    
+                    if (first instanceof Integer) {
+                        return ((Integer) first).compareTo((Integer) second);
+                    }
+                    if (first instanceof Long) {
+                        return ((Long) first).compareTo((Long) second);
+                    }
+                    if (first instanceof Double) {
+                        return ((Double) first).compareTo((Double) second);
+                    }
+                    if (first instanceof Float) {
+                        return ((Float) first).compareTo((Float) second);
+                    }
+                    if (first instanceof Byte) {
+                        return ((Byte) first).compareTo((Byte) second);
+                    }
+                    if (first instanceof Short) {
+                        return ((Short) first).compareTo((Short) second);
+                    }
+                }
+                // We are going to compare values of two different types.
+                
+                // Handle infinity before we try conversion to BigDecimal, as that BigDecimal can't represent that:
+                if (first instanceof Double) {
+                    double firstD = first.doubleValue();
+                    if (Double.isInfinite(firstD)) {
+                        if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(second)) {
+                            return  firstD == Double.NEGATIVE_INFINITY ? -1 : 1;
+                        }
+                        if (second instanceof Float) {
+                            return Double.compare(firstD, second.doubleValue());
+                        }
+                    }
+                }
+                if (first instanceof Float) {
+                    float firstF = first.floatValue();
+                    if (Float.isInfinite(firstF)) {
+                        if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(second)) {
+                            return firstF == Float.NEGATIVE_INFINITY ? -1 : 1;
+                        }
+                        if (second instanceof Double) {
+                            return Double.compare(firstF, second.doubleValue());
+                        }
+                    }
+                }
+                if (second instanceof Double) {
+                    double secondD = second.doubleValue();
+                    if (Double.isInfinite(secondD)) {
+                        if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(first)) {
+                            return secondD == Double.NEGATIVE_INFINITY ? 1 : -1;
+                        }
+                        if (first instanceof Float) {
+                            return Double.compare(first.doubleValue(), secondD);
+                        }
+                    }
+                }
+                if (second instanceof Float) {
+                    float secondF = second.floatValue();
+                    if (Float.isInfinite(secondF)) {
+                        if (NumberUtil.hasTypeThatIsKnownToNotSupportInfiniteAndNaN(first)) {
+                            return secondF == Float.NEGATIVE_INFINITY ? 1 : -1;
+                        }
+                        if (first instanceof Double) {
+                            return Double.compare(first.doubleValue(), secondF);
+                        }
+                    }
+                }
+                
+                return toBigDecimal(first).compareTo(toBigDecimal(second));
             }
         }
     
@@ -526,10 +599,33 @@ public abstract class ArithmeticEngine {
         }
     }
 
+    /**
+     * Convert a {@code Number} to {@link BigDecimal}.
+     * 
+     * @throws NumberFormatException
+     *             If the conversion is not possible, e.g. Infinite and NaN can't be converted to {@link BigDecimal}.
+     */
     private static BigDecimal toBigDecimal(Number num) {
+        if (num instanceof BigDecimal) {
+            return (BigDecimal) num;
+        }
+        if (num instanceof Integer || num instanceof Long || num instanceof Byte || num instanceof Short) {
+            return BigDecimal.valueOf(num.longValue());
+        }
+        if (num instanceof BigInteger) {
+            return new BigDecimal((BigInteger) num);
+        }
         try {
-            return num instanceof BigDecimal ? (BigDecimal) num : new BigDecimal(num.toString());
+            // Why toString? It's partly for backward compatibility. But it's also better for Double (and Float) values
+            // than new BigDecimal(someDouble), which is overly precise. For example, if you have `double d = 0.1`, then
+            // `new BigDecimal(d).equals(new BigDecimal("0.1"))` is `false`, while
+            // `new BigDecimal(Double.toString(d)).equals(new BigDecimal("0.1"))` is `true`.
+            return new BigDecimal(num.toString());
         } catch (NumberFormatException e) {
+            if (NumberUtil.isInfinite(num)) {
+                throw new NumberFormatException("It's impossible to convert an infinte value ("
+                        + num.getClass().getSimpleName() + " " + num + ") to BigDecimal.");
+            }
             // The exception message is useless, so we add a new one:
             throw new NumberFormatException("Can't parse this as BigDecimal number: " + StringUtil.jQuote(num));
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b996adaa/src/main/java/freemarker/template/utility/NumberUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/utility/NumberUtil.java b/src/main/java/freemarker/template/utility/NumberUtil.java
index a22deff..9cd3382 100644
--- a/src/main/java/freemarker/template/utility/NumberUtil.java
+++ b/src/main/java/freemarker/template/utility/NumberUtil.java
@@ -41,7 +41,7 @@ public class NumberUtil {
             return ((Double) num).isInfinite();
         } else if (num instanceof Float) {
             return ((Float) num).isInfinite();
-        } else if (isNonFPNumberOfSupportedClass(num)) {
+        } else if (hasTypeThatIsKnownToNotSupportInfiniteAndNaN(num)) {
             return false;
         } else {
             throw new UnsupportedNumberClassException(num.getClass());
@@ -53,7 +53,7 @@ public class NumberUtil {
             return ((Double) num).isNaN();
         } else if (num instanceof Float) {
             return ((Float) num).isNaN();
-        } else if (isNonFPNumberOfSupportedClass(num)) {
+        } else if (hasTypeThatIsKnownToNotSupportInfiniteAndNaN(num)) {
             return false;
         } else {
             throw new UnsupportedNumberClassException(num.getClass());
@@ -114,7 +114,14 @@ public class NumberUtil {
         // condition, but stripTrailingZeros was slower than setScale + compareTo.
     }
     
-    private static boolean isNonFPNumberOfSupportedClass(Number num) {
+    /**
+     * Tells if the type of the parameter number is known to not be able to represent infinite (positive or negative)
+     * and NaN. If this returns {@code false}, that doesn't mean that it can do that, because it's maybe just that this
+     * utility doesn't know that type.
+     * 
+     * @since 2.3.28
+     */
+    public static boolean hasTypeThatIsKnownToNotSupportInfiniteAndNaN(Number num) {
         return num instanceof Integer || num instanceof BigDecimal || num instanceof Long
                 || num instanceof Short || num instanceof Byte || num instanceof BigInteger;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b996adaa/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index e4cf87a..db1d1f3 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -27468,6 +27468,17 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>The default arithmetic engine
+              (<literal>ArithmeticEngine.BIGDECIMAL_ENGINE</literal>) can now
+              compare infinite (both positive and negative) to any other
+              standard type. Earlier, since <literal>BigDecimal</literal>
+              can't represent infinite, it was only working in certain special
+              cases. Also did some performance optimizations to slightly
+              decrease the impact and number of conversions to
+              <literal>BigDecimal</literal>.</para>
+            </listitem>
+
+            <listitem>
               <para>Added
               <literal>TemplateModelUtils.getKeyValuePairIterator(TemplateHashModelEx)</literal>
               static utility class, which can be used to get a

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b996adaa/src/test/java/freemarker/core/BigDecimalArithmeticEngineTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/BigDecimalArithmeticEngineTest.java b/src/test/java/freemarker/core/BigDecimalArithmeticEngineTest.java
new file mode 100644
index 0000000..d6db2f7
--- /dev/null
+++ b/src/test/java/freemarker/core/BigDecimalArithmeticEngineTest.java
@@ -0,0 +1,97 @@
+/*
+ * 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 static freemarker.core.ArithmeticEngine.*;
+import static org.junit.Assert.*;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.junit.Test;
+
+public class BigDecimalArithmeticEngineTest {
+
+    @Test
+    public void compareNumbersZeroTest() {
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(0, new BigDecimal("-0")));
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(0.0, new BigDecimal("-0")));
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(-0.0, new BigDecimal("+0")));
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(-0.0, 0.0));
+    }
+
+    @Test
+    public void compareNumbersNoRoundingGlitchTest() {
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(1.1, new BigDecimal("1.1")));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(Double.MIN_VALUE, Math.nextUp(Double.MIN_VALUE)));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(
+                Double.MIN_VALUE, new BigDecimal("" + Math.nextUp(Double.MIN_VALUE))));
+    }
+
+    @Test
+    public void compareNumbersSameTypeTest() {
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(1, 2));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(1L, 2L));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers((short) 1, (short) 2));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers((byte) 1, (byte) 2));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(1.0, 2.0));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(1.0f, 2.0f));
+        assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(BigDecimal.ONE, BigDecimal.TEN));
+    }
+
+    @Test
+    public void compareNumbersScaleDoesNotMatterTest() {
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(1.0, new BigDecimal("1")));
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(1.0, new BigDecimal("1.00")));
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(1.0f, new BigDecimal("0.1E1")));
+        assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(1, new BigDecimal("1.0")));
+    }
+
+    @Test
+    public void compareNumbersInfinityTest() {
+        for (boolean isFloat : new boolean[] { false, true }) {
+            Number negInf = isFloat ? Float.NEGATIVE_INFINITY : Double.NEGATIVE_INFINITY;
+            Number posInf = isFloat ? Float.POSITIVE_INFINITY : Double.POSITIVE_INFINITY;
+            
+            assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(negInf, posInf));
+            assertEquals(1, BIGDECIMAL_ENGINE.compareNumbers(posInf, negInf));
+            assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(posInf, posInf));
+            assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(negInf, negInf));
+            
+            Number otherNegInf = !isFloat ? Float.NEGATIVE_INFINITY : Double.NEGATIVE_INFINITY;
+            Number otherPosInf = !isFloat ? Float.POSITIVE_INFINITY : Double.POSITIVE_INFINITY;
+            assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(negInf, otherPosInf));
+            assertEquals(1, BIGDECIMAL_ENGINE.compareNumbers(posInf, otherNegInf));
+            assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(posInf, otherPosInf));
+            assertEquals(0, BIGDECIMAL_ENGINE.compareNumbers(negInf, otherNegInf));
+            
+            for (Number one : new Number[] {
+                    BigDecimal.ONE, 1.0, 1f, 1, 1L, (byte) 1, (short) 1, BigInteger.ONE,
+                    BigDecimal.ONE.negate(), -1.0, -1f, -1, -1L, (byte) -1, (short) -1, BigInteger.ONE.negate(),
+                    BigDecimal.ZERO, 0.0, 0f, 0, 0L, (byte) 0, (short) 0, BigInteger.ZERO }) {
+                assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(negInf, one));
+                assertEquals(1, BIGDECIMAL_ENGINE.compareNumbers(posInf, one));
+                assertEquals(1, BIGDECIMAL_ENGINE.compareNumbers(one, negInf));
+                assertEquals(-1, BIGDECIMAL_ENGINE.compareNumbers(one, posInf));
+            }
+        }
+    }
+
+}


[4/5] incubator-freemarker git commit: 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.

Posted by dd...@apache.org.
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/0c77097a
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/0c77097a
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/0c77097a

Branch: refs/heads/2.3
Commit: 0c77097aac210ba6b89e957ccaa6d4ca160dee21
Parents: 15e8ac3
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 12 07:02:51 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 12 07:02:51 2018 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/core/BuiltIn.java      |  4 +-
 .../freemarker/core/BuiltInsForSequences.java   | 68 +++++++++++++++
 src/manual/en_US/book.xml                       | 52 ++++++++++++
 src/test/java/freemarker/core/MinMaxBITest.java | 87 ++++++++++++++++++++
 4 files changed, 210 insertions(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0c77097a/src/main/java/freemarker/core/BuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index 78b9e45..6a6ad37 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -84,7 +84,7 @@ abstract class BuiltIn extends Expression implements Cloneable {
 
     static final Set<String> CAMEL_CASE_NAMES = new TreeSet<String>();
     static final Set<String> SNAKE_CASE_NAMES = new TreeSet<String>();
-    static final int NUMBER_OF_BIS = 266;
+    static final int NUMBER_OF_BIS = 268;
     static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
 
     static {
@@ -245,6 +245,8 @@ abstract class BuiltIn extends Expression implements Cloneable {
         putBI("node_namespace", "nodeNamespace", new node_namespaceBI());
         putBI("node_type", "nodeType", new node_typeBI());
         putBI("no_esc", "noEsc", new no_escBI());
+        putBI("max", new BuiltInsForSequences.maxBI());
+        putBI("min", new BuiltInsForSequences.minBI());
         putBI("number", new BuiltInsForStringsMisc.numberBI());
         putBI("number_to_date", "numberToDate", new number_to_dateBI(TemplateDateModel.DATE));
         putBI("number_to_time", "numberToTime", new number_to_dateBI(TemplateDateModel.TIME));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0c77097a/src/main/java/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index a58c7f6..9bc8d95 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -884,6 +884,74 @@ class BuiltInsForSequences {
         }
     }
  
+    private static abstract class MinOrMaxBI extends BuiltIn {
+        
+        private final int comparatorOperator;
+        
+        protected MinOrMaxBI(int comparatorOperator) {
+            this.comparatorOperator = comparatorOperator;
+        }
+
+        @Override
+        TemplateModel _eval(Environment env)
+                throws TemplateException {
+            TemplateModel model = target.eval(env);
+            if (model instanceof TemplateCollectionModel) {
+                return calculateResultForColletion((TemplateCollectionModel) model, env);
+            } else if (model instanceof TemplateSequenceModel) {
+                return calculateResultForSequence((TemplateSequenceModel) model, env);
+            } else {
+                throw new NonSequenceOrCollectionException(target, model, env);
+            }
+        }        
+
+        private TemplateModel calculateResultForColletion(TemplateCollectionModel coll, Environment env)
+        throws TemplateException {
+            TemplateModel best = null;
+            TemplateModelIterator iter = coll.iterator();
+            while (iter.hasNext()) {
+                TemplateModel cur = iter.next();
+                if (cur != null
+                        && (best == null || EvalUtil.compare(cur, null, comparatorOperator, null, best,
+                                    null, this, true, false, false, false, env))) {
+                    best = cur;
+                }
+            }
+            return best;
+        }
+        
+        private TemplateModel calculateResultForSequence(TemplateSequenceModel seq, Environment env)
+        throws TemplateException {
+            TemplateModel best = null;
+            for (int i = 0; i < seq.size(); i++) {
+                TemplateModel cur = seq.get(i);
+                if (cur != null
+                        && (best == null || EvalUtil.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(EvalUtil.CMP_OP_GREATER_THAN);
+        }
+        
+    }
+
+    static class minBI extends MinOrMaxBI {
+
+        public minBI() {
+            super(EvalUtil.CMP_OP_LESS_THAN);
+        }
+        
+    }
+    
     // Can't be instantiated
     private BuiltInsForSequences() { }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0c77097a/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index db1d1f3..4b89a2f 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -12740,6 +12740,14 @@ grant codeBase "file:/path/to/freemarker.jar"
           </listitem>
 
           <listitem>
+            <para><link linkend="ref_builtin_min_max">max</link></para>
+          </listitem>
+
+          <listitem>
+            <para><link linkend="ref_builtin_min_max">min</link></para>
+          </listitem>
+
+          <listitem>
             <para><link
             linkend="ref_builtin_namespace">namespace</link></para>
           </listitem>
@@ -16664,6 +16672,41 @@ red, green, blue.
           die with error if the sequence is empty.</para>
         </section>
 
+        <section xml:id="ref_builtin_min_max">
+          <title>min, max</title>
+
+          <indexterm>
+            <primary>min built-in</primary>
+          </indexterm>
+
+          <indexterm>
+            <primary>max built-in</primary>
+          </indexterm>
+
+          <para>Returns the smaller (<literal>min</literal>) or greatest
+          (<literal>max</literal>) item of the sequence (or collection). The
+          items must be either all numbers, or all date/time values of the
+          same kind (date-only, time-only, date-time), or else a comparison
+          error will occur. These are the same restrictions as for the <link
+          linkend="dgui_template_exp_comparison"><literal>&lt;</literal> and
+          <literal>&gt;</literal> operators</link>.</para>
+
+          <para>Missing items (i.e., Java <literal>null</literal>-s) will be
+          silently ignored. If the sequence is empty or it only contains
+          missing (Java <literal>null</literal>) items, the result itself will
+          be missing.</para>
+
+          <para>Example:</para>
+
+          <programlisting role="template">${[1, 2, 3]?min}
+${[1, 2, 3]?max}
+${[]?min!'-'}</programlisting>
+
+          <programlisting role="output">1
+3
+-</programlisting>
+        </section>
+
         <section xml:id="ref_builtin_reverse">
           <title>reverse</title>
 
@@ -27375,6 +27418,15 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>Added new built-ins: <literal>sequence?min</literal> and
+              <literal>sequence?max</literal> (<link
+              xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-86">FREEMARKER-86</link>),
+              which return the smallest and greatest item from a list of
+              numbers or date/time/date-times. <link
+              linkend="ref_builtin_min_max">See more here...</link></para>
+            </listitem>
+
+            <listitem>
               <para>Bug fixed (<link
               xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-83">FREEMARKER-83</link>);
               this fix is only active when <link

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/0c77097a/src/test/java/freemarker/core/MinMaxBITest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/MinMaxBITest.java b/src/test/java/freemarker/core/MinMaxBITest.java
new file mode 100644
index 0000000..3bff7ba
--- /dev/null
+++ b/src/test/java/freemarker/core/MinMaxBITest.java
@@ -0,0 +1,87 @@
+/*
+ * 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.sql.Time;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import freemarker.template.DefaultIterableAdapter;
+import freemarker.template.utility.DateUtil;
+import freemarker.template.utility.ObjectWrapperWithAPISupport;
+import freemarker.test.TemplateTest;
+
+public class MinMaxBITest extends TemplateTest {
+
+    @Test
+    public void basicsTest() throws Exception {
+        getConfiguration().setTimeZone(DateUtil.UTC);
+        getConfiguration().setTimeFormat("HH:mm:ss");
+        
+        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");
+    }
+    
+}


[5/5] incubator-freemarker git commit: Merge remote-tracking branch 'origin/2.3-gae' into 2.3

Posted by dd...@apache.org.
Merge remote-tracking branch 'origin/2.3-gae' into 2.3


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/0c69b198
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/0c69b198
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/0c69b198

Branch: refs/heads/2.3
Commit: 0c69b19832c4cc2fb843080b00b281216d06618e
Parents: 5bfa9ac 0c77097
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 12 07:06:02 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 12 07:06:02 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/core/ArithmeticEngine.java  | 110 +++++++++++++++++--
 src/main/java/freemarker/core/BuiltIn.java      |   4 +-
 .../freemarker/core/BuiltInsForSequences.java   |  68 ++++++++++++
 .../template/DefaultIterableAdapter.java        |   2 +-
 .../freemarker/template/utility/NumberUtil.java |  13 ++-
 src/manual/en_US/book.xml                       |  67 ++++++++++-
 .../core/BigDecimalArithmeticEngineTest.java    |  97 ++++++++++++++++
 src/test/java/freemarker/core/MinMaxBITest.java |  87 +++++++++++++++
 8 files changed, 434 insertions(+), 14 deletions(-)
----------------------------------------------------------------------



[3/5] incubator-freemarker git commit: (JavaDoc typo)

Posted by dd...@apache.org.
(JavaDoc typo)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/15e8ac35
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/15e8ac35
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/15e8ac35

Branch: refs/heads/2.3
Commit: 15e8ac35bbcb652c49c55b88d7c7496834f03f71
Parents: b996ada
Author: ddekany <dd...@apache.org>
Authored: Mon Mar 12 06:38:29 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Mon Mar 12 06:38:29 2018 +0100

----------------------------------------------------------------------
 src/main/java/freemarker/template/DefaultIterableAdapter.java | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/15e8ac35/src/main/java/freemarker/template/DefaultIterableAdapter.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/DefaultIterableAdapter.java b/src/main/java/freemarker/template/DefaultIterableAdapter.java
index 824e2d7..7438ff9 100644
--- a/src/main/java/freemarker/template/DefaultIterableAdapter.java
+++ b/src/main/java/freemarker/template/DefaultIterableAdapter.java
@@ -33,7 +33,7 @@ import freemarker.template.utility.ObjectWrapperWithAPISupport;
  * 
  * <p>
  * Thread safety: A {@link DefaultIterableAdapter} is as thread-safe as the {@link Iterable} that it wraps is. Normally
- * you only have to consider read-only access, as the FreeMarker template language doesn't provide mean to call
+ * you only have to consider read-only access, as the FreeMarker template language doesn't provide means to call
  * {@link Iterator} modifier methods (though of course, Java methods called from the template can violate this rule).
  * 
  * <p>