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 2022/12/16 11:36:48 UTC

[freemarker] branch 2.3-gae updated (77cc7ed7 -> 50825718)

This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a change to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git


    from 77cc7ed7 Disabled Google Analytics, as per ASF Privacy Policy. Updated footer link section to be similar to what we have in freemarker-site.
     new f4a42db1 Refactorings/changed related to computer number format (like ?c): - If incompatible_improvements >= 2.3.32: ?c and number_format "computer" does lossless conversion, and possibly outputs in exponential form. - Added number_format "c" as alias to "computer". - JavaTemplateNumberFormatFactory doesn't deal with "computer"/"c" anymore, so we could get rid of the related cache key workaround too - In Environment, added getCTemplateNumberFormat(), and deprecated getCNumberFormat().
     new 50825718 Typo/grammar fixes in messages and comments

The 2 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../java/freemarker/core/ArithmeticEngine.java     |   2 +-
 .../freemarker/core/BuiltInsForMultipleTypes.java  | 109 ++++++-----
 .../freemarker/core/CTemplateNumberFormat.java     | 178 +++++++++++++++++
 src/main/java/freemarker/core/Configurable.java    |   3 +-
 src/main/java/freemarker/core/Environment.java     |  46 +++--
 .../core/JavaTemplateNumberFormatFactory.java      |   8 +-
 .../java/freemarker/core/TemplateNumberFormat.java |   5 +-
 .../java/freemarker/core/_ArrayEnumeration.java    |   2 +-
 src/main/java/freemarker/core/_ArrayIterator.java  |   2 +-
 src/main/java/freemarker/core/_DelayedAOrAn.java   |   2 +-
 .../core/_DelayedConversionToString.java           |   2 +-
 .../core/_DelayedFTLTypeDescription.java           |   2 +-
 .../freemarker/core/_DelayedGetCanonicalForm.java  |   2 +-
 .../java/freemarker/core/_DelayedGetMessage.java   |   2 +-
 .../core/_DelayedGetMessageWithoutStackTop.java    |   2 +-
 src/main/java/freemarker/core/_DelayedJQuote.java  |   2 +-
 .../freemarker/core/_DelayedJoinWithComma.java     |   2 +-
 .../freemarker/core/_ErrorDescriptionBuilder.java  |   2 +-
 src/main/java/freemarker/core/_Java8.java          |   2 +-
 src/main/java/freemarker/core/_Java8Impl.java      |   2 +-
 src/main/java/freemarker/core/_JavaVersions.java   |   2 +-
 src/main/java/freemarker/core/_MessageUtil.java    |   2 +-
 .../_ObjectBuilderSettingEvaluationException.java  |   2 +-
 .../core/_ObjectBuilderSettingEvaluator.java       |   4 +-
 .../core/_SettingEvaluationEnvironment.java        |   2 +-
 src/main/java/freemarker/core/_SortedArraySet.java |   2 +-
 ..._UnexpectedTypeErrorExplainerTemplateModel.java |   2 +-
 .../freemarker/core/_UnmodifiableCompositeSet.java |   2 +-
 .../java/freemarker/core/_UnmodifiableSet.java     |   2 +-
 .../java/freemarker/template/Configuration.java    |  16 +-
 src/manual/en_US/book.xml                          | 216 +++++++++++++++------
 .../freemarker/core/CTemplateNumberFormatTest.java | 141 ++++++++++++++
 .../test/templatesuite/expected/number-format.txt  |   9 +-
 .../test/templatesuite/templates/number-format.ftl |  18 +-
 34 files changed, 632 insertions(+), 165 deletions(-)
 create mode 100644 src/main/java/freemarker/core/CTemplateNumberFormat.java
 create mode 100644 src/test/java/freemarker/core/CTemplateNumberFormatTest.java


[freemarker] 01/02: Refactorings/changed related to computer number format (like ?c): - If incompatible_improvements >= 2.3.32: ?c and number_format "computer" does lossless conversion, and possibly outputs in exponential form. - Added number_format "c" as alias to "computer". - JavaTemplateNumberFormatFactory doesn't deal with "computer"/"c" anymore, so we could get rid of the related cache key workaround too - In Environment, added getCTemplateNumberFormat(), and deprecated getCNumberFormat().

Posted by dd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git

commit f4a42db16b5abfd3b9f14000ab4dac0e66a41541
Author: ddekany <dd...@apache.org>
AuthorDate: Fri Dec 16 09:52:14 2022 +0100

    Refactorings/changed related to computer number format (like ?c):
    - If incompatible_improvements >= 2.3.32: ?c and number_format "computer" does lossless conversion, and possibly outputs in exponential form.
    - Added number_format "c" as alias to "computer".
    - JavaTemplateNumberFormatFactory doesn't deal with "computer"/"c" anymore, so we could get rid of the related cache key workaround too
    - In Environment, added getCTemplateNumberFormat(), and deprecated getCNumberFormat().
---
 .../freemarker/core/BuiltInsForMultipleTypes.java  | 109 ++++++-----
 .../freemarker/core/CTemplateNumberFormat.java     | 178 +++++++++++++++++
 src/main/java/freemarker/core/Configurable.java    |   3 +-
 src/main/java/freemarker/core/Environment.java     |  46 +++--
 .../core/JavaTemplateNumberFormatFactory.java      |   8 +-
 .../java/freemarker/template/Configuration.java    |  14 ++
 src/manual/en_US/book.xml                          | 216 +++++++++++++++------
 .../freemarker/core/CTemplateNumberFormatTest.java | 141 ++++++++++++++
 .../test/templatesuite/expected/number-format.txt  |   9 +-
 .../test/templatesuite/templates/number-format.ftl |  18 +-
 10 files changed, 605 insertions(+), 137 deletions(-)

diff --git a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
index fbd528cc..87cce1fb 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -71,63 +71,72 @@ class BuiltInsForMultipleTypes {
             }
             
         }
-        
-        private final BIBeforeICI2d3d21 prevICIObj = new BIBeforeICI2d3d21();
 
-        @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);
+        static class BIBeforeICI2d3d32 extends AbstractCBI implements ICIChainMember {
+            private final BIBeforeICI2d3d21 prevICIObj = new BIBeforeICI2d3d21();
+
+            @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());
+                // INF etc. is properly handled by ?c since 2.3.21, but the getCNumberFormat returns the pre 2.3.21
+                // format before IcI 2.3.31, so we keep these here:
+                } 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));
+            }
+
+            @Override
+            public int getMinimumICIVersion() {
+                return _TemplateAPI.VERSION_INT_2_3_21;
+            }
+
+            @Override
+            public Object getPreviousICIChainMember() {
+                return prevICIObj;
             }
         }
 
         @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
+        protected TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateException {
+            try {
+                return new SimpleScalar(CTemplateNumberFormat.INSTANCE.formatToPlainText((TemplateNumberModel) model));
+            } catch (TemplateValueFormatException e) {
+                throw _MessageUtil.newCantFormatNumberException(CTemplateNumberFormat.INSTANCE, target, e, false);
             }
-        
-            return new SimpleScalar(env.getCNumberFormat().format(num));
         }
 
+        private final BIBeforeICI2d3d32 prevICIObj = new BIBeforeICI2d3d32();
+
         @Override
         public int getMinimumICIVersion() {
-            return _TemplateAPI.VERSION_INT_2_3_21;
+            return _TemplateAPI.VERSION_INT_2_3_32;
         }
         
         @Override
@@ -795,7 +804,7 @@ class BuiltInsForMultipleTypes {
     static abstract class AbstractCBI extends BuiltIn {
         
         @Override
-        TemplateModel _eval(Environment env) throws TemplateException {
+        final TemplateModel _eval(Environment env) throws TemplateException {
             TemplateModel model = target.eval(env);
             if (model instanceof TemplateNumberModel) {
                 return formatNumber(env, model);
@@ -809,8 +818,8 @@ class BuiltInsForMultipleTypes {
                         env);
             }
         }
-    
-        protected abstract TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateModelException;
+
+        protected abstract TemplateModel formatNumber(Environment env, TemplateModel model) throws TemplateException;
         
     }
 
diff --git a/src/main/java/freemarker/core/CTemplateNumberFormat.java b/src/main/java/freemarker/core/CTemplateNumberFormat.java
new file mode 100644
index 00000000..139e2d72
--- /dev/null
+++ b/src/main/java/freemarker/core/CTemplateNumberFormat.java
@@ -0,0 +1,178 @@
+/*
+ * 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.math.BigDecimal;
+import java.math.BigInteger;
+
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+
+/**
+ * @since 2.3.32
+ */
+final class CTemplateNumberFormat extends TemplateNumberFormat {
+    static final TemplateNumberFormat INSTANCE = new CTemplateNumberFormat();
+
+    private static final float MAX_INCREMENT_1_FLOAT = 16777216f;
+    private static final double MAX_INCREMENT_1_DOUBLE = 9007199254740992d;
+
+    private CTemplateNumberFormat() {
+    }
+
+    @Override
+    public String formatToPlainText(TemplateNumberModel numberModel) throws TemplateValueFormatException,
+            TemplateModelException {
+        Number num = TemplateFormatUtil.getNonNullNumber(numberModel);
+
+        if (num instanceof Integer || num instanceof Long) {
+            // Accelerate these fairly common cases
+            return num.toString();
+        } else if (num instanceof Double) {
+            double n = num.doubleValue();
+            if (n == Double.POSITIVE_INFINITY) {
+                return "INF";
+            }
+            if (n == Double.NEGATIVE_INFINITY) {
+                return "-INF";
+            }
+            if (Double.isNaN(n)) {
+                return "NaN";
+            }
+            if (Math.floor(n) == n) {
+                if (Math.abs(n) <= MAX_INCREMENT_1_DOUBLE) {
+                    return Long.toString((long) n);
+                }
+                // We let this fall through to exponential form, despite that we certainly haven't reached the 100
+                // digits limit, because ulp > 1, so if this was an ID, its value is potentially corrupted anyway.
+            } else { // Non-whole number
+                double absN = Math.abs(n);
+                // The range where Double.toString already uses exponential form, but BigDecimal toString doesn't yet.
+                if (absN < 1E-3 && absN > 1E-7) {
+                    // Always use BigDecimal.valueOf(n), not new BigDecimal(n)! For example, BigDecimal.valueOf(1.9) is
+                    // just the BigDecimal 1.9, but new BigDecimal(1.9) is a very long BigDecimal starting with 1.899.
+                    return BigDecimal.valueOf(n).toString();
+                }
+                // Avoid exponential form where we don't use it with BigDecimal-s. We don't worry about the 100 digits
+                // limit, as such big numbers are whole numbers.
+                if (absN >= 1E7) {
+                    // No new BigDecimal(n)! See earlier why.
+                    return BigDecimal.valueOf(n).toPlainString();
+                }
+            }
+            // Remove redundant ".0" so that result look the same as with BigDecimal
+            return removeRedundantDot0(Double.toString(n));
+        } else if (num instanceof Float) {
+            float n = num.floatValue();
+
+            if (n == Float.POSITIVE_INFINITY) {
+                return "INF";
+            }
+            if (n == Float.NEGATIVE_INFINITY) {
+                return "-INF";
+            }
+            if (Float.isNaN(n)) {
+                return "NaN";
+            }
+            if (Math.floor(n) == n) {
+                if (Math.abs(n) <= MAX_INCREMENT_1_FLOAT) {
+                    return Long.toString((long) n);
+                }
+                // We let this fall through to exponential form, despite that we certainly haven't reached the 100
+                // digits limit, because ulp > 1, so if this was an ID, its value is potentially corrupted anyway.
+            } else { // Non-whole number
+                float absN = Math.abs(n);
+                // The range where Float.toString already uses exponential form, but BigDecimal toString doesn't yet.
+                if (absN < 1E-3f && absN > 1E-7f) {
+                    // No new BigDecimal(n); see earlier why. But there's no BigDecimal.valueOf(float), so we do this.
+                    return new BigDecimal(num.toString()).toString();
+                }
+                // Unlike with double, we don't need an absN >= 1E7 branch, since those number are whole with float.
+            }
+            // Remove redundant ".0" so that result look the same as with BigDecimal
+            return removeRedundantDot0(Float.toString(n));
+        } else if (num instanceof BigInteger) {
+            return num.toString();
+        } else if (num instanceof BigDecimal) {
+            BigDecimal bd = ((BigDecimal) num).stripTrailingZeros();
+            int scale = bd.scale();
+            if (scale <= 0) {
+                // A whole number. Myabe a long ID in a database or other system, and for those exponential form is not
+                // expected generally, so we avoid that. But then, it becomes too easy to write something like
+                // 1e1000000000000 and kill the server with a terra byte long rendering of the number, so for lengths that
+                // realistically aren't ID-s or such, we use exponential format after all:
+                if (scale <= -100) {
+                    return bd.toString(); // Will give exponential form for this scale
+                }
+                return bd.toPlainString(); // Never gives exponential form
+            }
+            // `db` is not a whole number. Note that `bd` is already normalized to not have trailing zeroes.
+            return bd.toString(); // Gives exponential form of the absolute value of the number is less than 1E-7
+        } else {
+            // We don't know what this is, but as we want the format to be lossless, this is our best guess.
+            return num.toString();
+        }
+    }
+
+    /**
+     * Like "1.0" to "1", and "1.0E-7" to "1E-7". Designed to work on Float/Double.toString() result only.
+     */
+    private static String removeRedundantDot0(String s) {
+        int len = s.length();
+        for (int i = 0; i < len; i++) {
+            char c = s.charAt(i);
+            if (c == '.') {
+                i++;
+                if (s.charAt(i) == '0') {
+                    i++;
+                    if (i == len) {
+                        return s.substring(0, len - 2);
+                    }
+                    if (s.charAt(i) == 'E') {
+                        char[] result = new char[s.length() - 2];
+                        int dst = 0;
+                        for (int src = 0; src < i - 2; src++) {
+                            result[dst++] = s.charAt(src);
+                        }
+                        for (int src = i; src < len; src++) {
+                            result[dst++] = s.charAt(src);
+                        }
+                        return String.valueOf(result);
+                    }
+                }
+                break;
+            }
+        }
+        return s;
+    }
+
+    @Override
+    public boolean isLocaleBound() {
+        return false;
+    }
+
+    @Override
+    public String getDescription() {
+        return "c";
+    }
+
+
+}
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 8b2e0e0d..676bd7f1 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -814,7 +814,8 @@ public class Configurable {
      *   <li>{@code "number"}: The number format returned by {@link NumberFormat#getNumberInstance(Locale)}</li>
      *   <li>{@code "currency"}: The number format returned by {@link NumberFormat#getCurrencyInstance(Locale)}</li>
      *   <li>{@code "percent"}: The number format returned by {@link NumberFormat#getPercentInstance(Locale)}</li>
-     *   <li>{@code "computer"}: The number format used by FTL's {@code c} built-in (like in {@code someNumber?c}).</li>
+     *   <li>{@code "c"} (recognized since 2.3.34), or {@code "computer"} (same as {@code "c"}, but also recognized by
+     *       older versions): The number format used by FTL's {@code c} built-in (like in {@code someNumber?c}).</li>
      *   <li>{@link java.text.DecimalFormat} pattern (like {@code "0.##"}). This syntax is extended by FreeMarker
      *       so that you can specify options like the rounding mode and the symbols used after a 2nd semicolon. For
      *       example, {@code ",000;; roundingMode=halfUp groupingSeparator=_"} will format numbers like {@code ",000"}
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index b6b5b442..617bb48c 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -165,6 +165,7 @@ public final class Environment extends Configurable {
 
     @Deprecated
     private NumberFormat cNumberFormat;
+    private TemplateNumberFormat cTemplateNumberFormat;
 
     /**
      * Used by the "iso_" built-ins to accelerate formatting.
@@ -1666,6 +1667,9 @@ public final class Environment extends Configurable {
             }
 
             return formatFactory.get(params, locale, this);
+        } else if (formatStringLen >= 1 && formatString.charAt(0) == 'c'
+                && (formatStringLen == 1 || formatString.equals(COMPUTER))) {
+            return getCTemplateNumberFormat();
         } else {
             return JavaTemplateNumberFormatFactory.INSTANCE.get(formatString, locale, this);
         }
@@ -1676,8 +1680,34 @@ public final class Environment extends Configurable {
      * {@linkplain Configuration#setIncompatibleImprovements(Version) Incompatible Improvements} is less than 2.3.31,
      * this will wrongly give the format that the <tt>c</tt> built-in used before Incompatible Improvements 2.3.21.
      * See more at {@link Configuration#Configuration(Version)}.
+     *
+     * @deprecated Use {@link #getCTemplateNumberFormat()} instead. This method can't return the format used when
+     * {@linkplain Configuration#setIncompatibleImprovements(Version) Incompatible Improvements} is 2.3.32,
+     * or greater, and instead it will fall back to return the format that was used for 2.3.31.
      */
+    @Deprecated
     public NumberFormat getCNumberFormat() {
+        ensureCNumberFormatInitialized();
+        return cNumberFormat;
+    }
+
+    /**
+     * Returns the {@link TemplateNumberFormat} used for the <tt>c</tt> built-in uses.
+     * {@linkplain Configuration#setIncompatibleImprovements(Version) Incompatible Improvements} is 2.3.32 or greater.
+     *
+     * @since 2.3.32
+     */
+    public TemplateNumberFormat getCTemplateNumberFormat() {
+        if (configuration.getIncompatibleImprovements().intValue() < _TemplateAPI.VERSION_INT_2_3_32) {
+            ensureCNumberFormatInitialized();
+            return cTemplateNumberFormat;
+        }
+        return CTemplateNumberFormat.INSTANCE;
+    }
+
+    static final String COMPUTER = "computer";
+
+    private void ensureCNumberFormatInitialized() {
         // Note: DecimalFormat-s aren't thread-safe, so you must clone the static field value.
         if (cNumberFormat == null) {
             if (configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_31) {
@@ -1685,20 +1715,10 @@ public final class Environment extends Configurable {
             } else {
                 cNumberFormat = (DecimalFormat) C_NUMBER_FORMAT_ICI_2_3_20.clone();
             }
+            // Note this uses the legacy name "computer", instead of "c". From IcI 2.3.32 we are using
+            // CTemplateNumberFormat.INSTANCE instead, so users won't see this anymore.
+            cTemplateNumberFormat = new JavaTemplateNumberFormat(cNumberFormat, COMPUTER);
         }
-        return cNumberFormat;
-    }
-
-    /**
-     * As we have a number format cache that's shared between {@link Configuration}-s, if the interpretation of a format
-     * is impacted by Incompatible Improvements, we must change the cache key.
-     */
-    String transformNumberFormatGlobalCacheKey(String keyPart) {
-        if (configuration.getIncompatibleImprovements().intValue() >= _TemplateAPI.VERSION_INT_2_3_31
-                && JavaTemplateNumberFormatFactory.COMPUTER.equals(keyPart)) {
-            return "computer\u00002";
-        }
-        return keyPart;
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java b/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java
index 8e8a3279..f3bbe742 100644
--- a/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java
+++ b/src/main/java/freemarker/core/JavaTemplateNumberFormatFactory.java
@@ -32,8 +32,6 @@ class JavaTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
     
     static final JavaTemplateNumberFormatFactory INSTANCE = new JavaTemplateNumberFormatFactory();
 
-    static final String COMPUTER = "computer";
-
     private static final Logger LOG = Logger.getLogger("freemarker.runtime");
 
     private static final ConcurrentHashMap<CacheKey, NumberFormat> GLOBAL_FORMAT_CACHE
@@ -47,9 +45,7 @@ class JavaTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
     @Override
     public TemplateNumberFormat get(String params, Locale locale, Environment env)
             throws InvalidFormatParametersException {
-        CacheKey cacheKey = new CacheKey(
-                env != null ? env.transformNumberFormatGlobalCacheKey(params) : params,
-                locale);
+        CacheKey cacheKey = new CacheKey(params, locale);
         NumberFormat jFormat = GLOBAL_FORMAT_CACHE.get(cacheKey);
         if (jFormat == null) {
             if ("number".equals(params)) {
@@ -58,8 +54,6 @@ class JavaTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
                 jFormat = NumberFormat.getCurrencyInstance(locale);
             } else if ("percent".equals(params)) {
                 jFormat = NumberFormat.getPercentInstance(locale);
-            } else if (COMPUTER.equals(params)) {
-                jFormat = env.getCNumberFormat();
             } else {
                 try {
                     jFormat = ExtendedDecimalFormatParser.parse(params, locale);
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index ed47cba8..64d3b542 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -945,6 +945,20 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *       U+221E, and U+FFFD.
      *     </ul>
      *   </li>
+     *   <li><p>
+     *     2.3.32 (or higher):
+     *     <ul>
+     *       <li><p>The "computer" formatting of numbers (this is what {@code ?c} does, also setting the
+     *       {@code number_format} to {@code "c"}, or to {@code "computer"}) has changed for some non-whole numbers, and
+     *       for whole numbers with over 100 digits. It's now lossless, so it potentially shows much more decimals. It
+     *       uses exponential format (like 1.2E-7 instead of 0.00000012) for numbers whose absolute value is less
+     *       than 1E-6 (0.000001), and for whole numbers whose absolute value is at least 1E101 (so over 100 digits).
+     *       It also uses exponential format for whole floating point ({@code double}/{@code Double}}, or
+     *       {@code float}/{@code Float}) numbers, when their absolute value is too big for the floating point type to
+     *       store them precisely (so if the intent was to store some ID-s, they are likely corrupted anyway, as the
+     *       type skips some whole numbers).
+     *     </ul>
+     *   </li>
      * </ul>
      * 
      * @throws IllegalArgumentException
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index d47f66b9..30022452 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -20,10 +20,7 @@
 <book conformance="docgen" version="5.0" xml:lang="en"
       xmlns="http://docbook.org/ns/docbook"
       xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:ns5="http://www.w3.org/1998/Math/MathML"
-      xmlns:ns4="http://www.w3.org/1999/xhtml"
-      xmlns:ns3="http://www.w3.org/2000/svg"
-      xmlns:ns="http://docbook.org/ns/docbook">
+>
   <info>
     <title>Apache FreeMarker Manual</title>
 
@@ -15656,37 +15653,123 @@ rif: foo XYr baar</programlisting>
           That is, it formats with the rules that programming languages used
           to use, which is independent of all the locale and number format
           settings of FreeMarker. It always uses dot as decimal separator, and
-          it never uses grouping separators (like 3,000,000), nor exponential
-          form (like 5E20), nor superfluous leading or trailing 0-s (like 03
-          or 1.0), nor + sign (like +1). It will print at most 16 digits after
-          the decimal dot, and thus numbers whose absolute value is less than
-          1E-16 will be shown as 0. This built-in is crucial because be
-          default (like with <literal>${x}</literal>) numbers are converted to
-          strings with the locale (language, country) specific number
-          formatting, which is for human readers (like 3000000 is possibly
-          printed as 3,000,000). When the number is printed not for human
-          audience (e.g., for a database record ID used as the part of an URL,
-          or as invisible field value in a HTML form, or for printing
-          CSS/JavaScript numerical literals) this built-in must be used to
-          print the number (i.e., use <literal>${x?c}</literal> instead of
-          <literal>${x}</literal>), or else the output will be possibly broken
-          depending on the current number formatting settings and locale (like
-          the decimal point is not dot, but comma in many countries) and the
-          value of the number (like big numbers are possibly
-          <quote>damaged</quote> by grouping separators).</para>
-
-          <para>If the <literal>incompatible_improvements</literal> FreeMarker
-          configuration setting is set to 2.3.24 or higher (also if it's set
-          to 2.3.20 or higher and you are outside a string literal), this
-          built-in will return <literal>"INF"</literal>,
-          <literal>"-INF"</literal> and <literal>"NaN"</literal> for
-          positive/negative infinity and IEEE floating point Not-a-Number,
-          respectively. These are the XML Schema compatible representations of
-          these special values. (Earlier it has returned what
-          <literal>java.text.DecimalFormat</literal> did with US locale, none
-          of which is understood by any (common) computer language.)</para>
-
-          <para>Note that this built-in <link
+          it never uses grouping separators (like in 3,000,000), nor
+          <quote>+</quote> sign (like +1), nor superfluous leading or trailing
+          0-s (like 03 or 1.0). It usually avoids exponential form (like it
+          will never format 10000000000 as 1E10), but see the details below
+          regarding that.</para>
+
+          <para>This built-in is crucial because be default (like with
+          <literal>${x}</literal>) numbers are converted to strings with the
+          locale (language, country) specific number formatting, which is for
+          human readers (like 3000000 is possibly printed as 3,000,000). When
+          the number is printed not for human audience (e.g., for a database
+          record ID used as the part of an URL, or as invisible field value in
+          a HTML form, or for printing CSS/JavaScript numerical literals) you
+          must use this built-in to format the number (i.e., use
+          <literal>${x?c}</literal> instead of <literal>${x}</literal>), or
+          else the output will be possibly broken depending on the current
+          number formatting settings and locale (like the decimal point is not
+          dot, but comma in many countries) and the value of the number (like
+          big numbers are possibly <quote>damaged</quote> by grouping
+          separators).</para>
+
+          <para>Further details:</para>
+
+          <itemizedlist>
+            <listitem>
+              <para>Rounding: The conversion is only guaranteed to be lossless
+              if the <link
+              linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
+              FreeMarker configuration setting</link> is set to 2.3.32 or
+              higher. Before that, the format never uses exponential form, and
+              is limited to 16 digits after the decimal dot, so rounding can
+              occur (e.g. 1E-17 will be shown as 0).</para>
+            </listitem>
+
+            <listitem>
+              <para>Special floating point values: If the
+              <literal>incompatible_improvements</literal> FreeMarker
+              configuration setting is set to 2.3.24 or higher (also if it's
+              set to 2.3.20 or higher and you are outside a string literal),
+              this built-in will return <literal>"INF"</literal>,
+              <literal>"-INF"</literal> and <literal>"NaN"</literal> for
+              positive/negative infinity and IEEE floating point Not-a-Number,
+              respectively. These are the XML Schema compatible
+              representations of these special values. With too low
+              <literal>incompatible_improvements</literal> it returns what
+              <literal>java.text.DecimalFormat</literal> did with US locale,
+              none of which is understood by any (common) computer
+              language.</para>
+            </listitem>
+
+            <listitem>
+              <para>Exponential form: Before
+              <literal>incompatible_improvements</literal> 2.3.32, it was
+              never used. After that, it's uses exponential form in these
+              cases:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para>For a non-whole number whose absolute value is less
+                  than 1E-6 (0.000001).</para>
+                </listitem>
+
+                <listitem>
+                  <para>For a whole number that has more than 100 digits in
+                  non-exponential form</para>
+                </listitem>
+
+                <listitem>
+                  <para>For a whole numbers whose absolute value is too big
+                  for the backing floating point type to be safe from rounding
+                  errors. More specifically:</para>
+
+                  <itemizedlist>
+                    <listitem>
+                      <para>For a whole number that's stored in a
+                      <literal>double</literal>, or <literal>Double</literal>
+                      on the Java side (i.e., as 64 bit floating point
+                      number), and has an absolute value greater than
+                      9007199254740992. It's because that type can't store all
+                      whole numbers outside that range. So if the intent was
+                      to store some ID-s, they are likely already corrupted
+                      (because they had to be rounded to the closest whole
+                      number that the type can store), and you should use
+                      <literal>long</literal> or <literal>BigInteger</literal>
+                      on the Java side.</para>
+                    </listitem>
+
+                    <listitem>
+                      <para>For a whole (integer) value that's stored in a
+                      <literal>float</literal>, or <literal>Float</literal> on
+                      the Java side (i.e., as 32 bit floating point number),
+                      and has an absolute value greater than 16777216.
+                      Reasoning is the same as for
+                      <literal>double</literal>.</para>
+                    </listitem>
+                  </itemizedlist>
+
+                  <para>Note that by default FreeMarker doesn't use
+                  <literal>double</literal> or <literal>float</literal>, so
+                  such values are likely can only come from the
+                  data-model.</para>
+                </listitem>
+              </itemizedlist>
+            </listitem>
+
+            <listitem>
+              <para>The output never contains superfluous zeros after the
+              decimal point. Thus, unlike in Java, you can't tell apart a
+              <literal>double</literal> from an <literal>int</literal>, if
+              they store the same number mathematically, because the output
+              will look the same for both. (This is because the template
+              language only have a single number type, so you don't have a
+              good control over the backing Java type.)</para>
+            </listitem>
+          </itemizedlist>
+
+          <para>Note that the <literal>c</literal> built-in <link
           linkend="ref_builtin_c_boolean">also works on
           booleans</link>.</para>
         </section>
@@ -15884,14 +15967,15 @@ rif: foo XYr baar</programlisting>
           configuration settings. You can also specify a number format
           explicitly with this built-in, as it will be shown later.</para>
 
-          <para>There are four predefined number formats:
-          <literal>computer</literal>, <literal>currency</literal>,
-          <literal>number</literal>, and <literal>percent</literal>. The exact
-          meaning of these is locale (nationality) specific, and is controlled
-          by the Java platform installation, not by FreeMarker, except for
-          <literal>computer</literal>, which uses the same formatting as <link
-          linkend="ref_builtin_c">the <literal>c</literal> built-in</link>
-          (assuming <link
+          <para>There are four predefined number formats: <literal>c</literal>
+          (since 2.3.32, before that it was called
+          <literal>computer</literal>, which still works),
+          <literal>currency</literal>, <literal>number</literal>, and
+          <literal>percent</literal>. The exact meaning of these is locale
+          (nationality) specific, and is controlled by the Java platform
+          installation, not by FreeMarker, except for <literal>c</literal>,
+          which uses the same formatting as <link linkend="ref_builtin_c">the
+          <literal>c</literal> built-in</link> (assuming <link
           linkend="pgui_config_incompatible_improvements">incompatible
           improvements</link> set to 2.3.31, or higher, or else infinity and
           NaN isn't formatted like that). There can also be programmer-defined
@@ -15905,7 +15989,7 @@ ${x?string}  &lt;#-- the same as ${x} --&gt;
 ${x?string.number}
 ${x?string.currency}
 ${x?string.percent}
-${x?string.computer}</programlisting>
+${x?string.c} &lt;#-- But you should write ${x?c} --&gt;</programlisting>
 
           <para>If your locale is US English, this will print:</para>
 
@@ -23800,9 +23884,10 @@ ${"'{}"}
                 </listitem>
 
                 <listitem>
-                  <para><literal>computer</literal>, which formats like <link
-                  linkend="ref_builtin_c">the <literal>c</literal>
-                  built-in</link></para>
+                  <para><literal>c</literal> since 2.3.24 (was called
+                  <literal>computer</literal> before that, which still works),
+                  which formats like <link linkend="ref_builtin_c">the
+                  <literal>c</literal> built-in</link></para>
                 </listitem>
 
                 <listitem>
@@ -29424,13 +29509,36 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
           <itemizedlist>
             <listitem>
-              <para>This is related to <literal>freemarker.ext.xml</literal>,
-              which is the very old, long deprecated XML wrapper (the commonly
-              used one is <literal>freemarker.ext.dom</literal>), which almost
-              nobody uses, or maybe truly nobody. Still, now the
-              <literal>_registerNamespace</literal> key works, doing what the
-              documentation always stated. Before this fix it just behaved as
-              if it was the name of an element you are looking for.</para>
+              <para>When setting the <literal>number_format</literal> setting
+              (also when formatting with
+              <literal>?string(<replaceable>format</replaceable>)</literal>),
+              now <quote>c</quote> can be used instead of
+              <quote>computer</quote>. The two mean the same, and
+              <quote>c</quote> is preferred from now on.</para>
+            </listitem>
+
+            <listitem>
+              <para>If <link
+              linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incomplatible_improvements</literal></link>
+              is set to 2.3.32 (or higher), the behavior of
+              <literal>?c</literal> (and thus the <quote>computer</quote> and
+              <quote>c</quote> number format) has changed for some non-whole
+              numbers, and for whole numbers with over 100 digits. The goal of
+              this change is to make the formatting lossless, and also to
+              avoiding huge output with exponents of high magnitude. See
+              details at the documentation of the <link
+              linkend="ref_builtin_c"><literal>c</literal>
+              built-in</link>.</para>
+            </listitem>
+
+            <listitem>
+              <para>In <literal>freemarker.ext.xml</literal>, which is the
+              old, long deprecated XML wrapper, that almost nobody uses
+              anymore (the commonly used one is
+              <literal>freemarker.ext.dom</literal>), the
+              <literal>_registerNamespace</literal> key now works, doing what
+              the documentation always stated. Before this fix it just behaved
+              as if it was the name of an element you are looking for.</para>
             </listitem>
           </itemizedlist>
         </section>
diff --git a/src/test/java/freemarker/core/CTemplateNumberFormatTest.java b/src/test/java/freemarker/core/CTemplateNumberFormatTest.java
new file mode 100644
index 00000000..3ae7ad75
--- /dev/null
+++ b/src/test/java/freemarker/core/CTemplateNumberFormatTest.java
@@ -0,0 +1,141 @@
+/*
+ * 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 org.junit.Assert.*;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+
+import org.junit.Test;
+
+import freemarker.template.SimpleNumber;
+import freemarker.template.TemplateModelException;
+import freemarker.template.utility.UnsupportedNumberClassException;
+
+public class CTemplateNumberFormatTest {
+    @Test
+    public void testFormatDouble() throws TemplateModelException, TemplateValueFormatException {
+        testFormat(1.0, "1");
+        testFormat(1.2, "1.2");
+        testFormat(9007199254740991d, "9007199254740991");
+        testFormat(9007199254740992d, "9007199254740992");
+        testFormat(9007199254740994d, "9.007199254740994E15");
+        testFormat(10000000000000000d, "1E16");
+        testFormat(12300000000000000d, "1.23E16");
+        testFormat(Double.NaN, "NaN");
+        testFormat(Double.POSITIVE_INFINITY, "INF");
+        testFormat(1.9E-6, "0.0000019");
+        testFormat(9.5E-7, "9.5E-7");
+        testFormat(9999999.5, "9999999.5");
+        testFormat(10000000.5, "10000000.5");
+    }
+
+    @Test
+    public void testFormatFloat() throws TemplateModelException, TemplateValueFormatException {
+        testFormat(1.0f, "1");
+        testFormat(1.2f, "1.2");
+        testFormat(16777215f, "16777215");
+        testFormat(16777216f, "16777216");
+        testFormat(16777218f, "1.6777218E7");
+        testFormat(100000000f, "1E8");
+        testFormat(123000000f, "1.23E8");
+        testFormat(Float.NaN, "NaN");
+        testFormat(Float.POSITIVE_INFINITY, "INF");
+        testFormat(1.9E-6f, "0.0000019");
+        testFormat(9.5E-7f, "9.5E-7");
+        testFormat(1000000.5f, "1000000.5");
+        // For float, values >= 1E7 has ulp >= 1, so we don't have to deal with non-wholes in that range.
+    }
+
+    @Test
+    public void testFormatBigInteger() throws TemplateModelException, TemplateValueFormatException {
+        testFormat(new BigInteger("-0"), "0");
+        testFormat(new BigInteger("1"), "1");
+        testFormat(new BigInteger("9000000000000000000000"), "9000000000000000000000");
+    }
+
+    @Test
+    public void testFormatBigDecimalWholeNumbers() throws TemplateModelException, TemplateValueFormatException {
+        testFormat(new BigDecimal("-0"), "0");
+        testFormat(new BigDecimal("1.0"), "1");
+        testFormat(new BigDecimal("10E-1"), "1");
+        testFormat(new BigDecimal("0.01E2"), "1");
+        testFormat(new BigDecimal("9000000000000000000000"), "9000000000000000000000");
+        testFormat(new BigDecimal("9e21"), "9000000000000000000000");
+        testFormat(new BigDecimal("9e100"), "9E+100");
+    }
+
+    @Test
+    public void testFormatBigDecimalNonWholeNumbers() throws TemplateModelException, TemplateValueFormatException {
+        testFormat(new BigDecimal("0.1"), "0.1");
+        testFormat(new BigDecimal("1E-1"), "0.1");
+        testFormat(new BigDecimal("0.0000001"), "1E-7");
+        testFormat(new BigDecimal("0.00000010"), "1E-7");
+        testFormat(new BigDecimal("0.000000999"), "9.99E-7");
+        testFormat(new BigDecimal("-0.0000001"), "-1E-7");
+        testFormat(new BigDecimal("0.000000123"), "1.23E-7");
+        testFormat(new BigDecimal("1E-6"), "0.000001");
+        testFormat(new BigDecimal("0.0000010"), "0.000001");
+        testFormat(new BigDecimal("1.0000000001"), "1.0000000001");
+        testFormat(new BigDecimal("1000000000.5"), "1000000000.5");
+    }
+
+    private void testFormat(Number n, String expectedResult) throws TemplateModelException,
+        TemplateValueFormatException {
+        String actualResult = (String) CTemplateNumberFormat.INSTANCE.format(new SimpleNumber(n));
+        assertFormatResult(n, actualResult, expectedResult);
+        if (!actualResult.equals("NaN") && !actualResult.equals("0") && !actualResult.startsWith("-")) {
+            Number negativeN = negate(n);
+            actualResult = (String) CTemplateNumberFormat.INSTANCE.format(new SimpleNumber(negativeN));
+            assertFormatResult(negativeN, actualResult, "-" + expectedResult);
+        }
+    }
+
+    private static Number negate(Number n) {
+        if (n instanceof Integer) {
+            return -n.intValue();
+        } else if (n instanceof BigDecimal) {
+            return ((BigDecimal) n).negate();
+        } else if (n instanceof Double) {
+            return -n.doubleValue();
+        } else if (n instanceof Float) {
+            return -n.floatValue();
+        } else if (n instanceof Long) {
+            return -n.longValue();
+        } else if (n instanceof Short) {
+            return -n.shortValue();
+        } else if (n instanceof Byte) {
+            return -n.byteValue();
+        } else if (n instanceof BigInteger) {
+            return ((BigInteger) n).negate();
+        } else {
+            throw new UnsupportedNumberClassException(n.getClass());
+        }
+    }
+
+    private void assertFormatResult(Number n, String actualResult, String expectedResult) {
+        if (!actualResult.equals(expectedResult)) {
+            fail("When formatting " + n + ", expected \"" + expectedResult + "\", but got "
+                    + "\"" + actualResult + "\".");
+        }
+    }
+
+}
\ No newline at end of file
diff --git a/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt b/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
index a6f84753..5d8237df 100644
--- a/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
+++ b/src/test/resources/freemarker/test/templatesuite/expected/number-format.txt
@@ -25,9 +25,6 @@
 1234567,89
 1234567.886
 1
-1
-1.000000000000001
-0.0000000000000001
--0.0000000000000001
-1
-0.0000000000000001
+100000.5
+100000.5
+100 000,5
\ No newline at end of file
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl b/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
index 82a14ed9..81501e54 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/number-format.ftl
@@ -28,12 +28,18 @@ ${1?c}
 ${1234567.886}
 ${1234567.886?c}
 ${int?c}
-${double?c}
-${double2?c}
-${double3?c}
-${double4?c}
-${bigDecimal?c}
-${bigDecimal2?c}
+<#setting number_format = "computer">
+${100000.5}
+<#setting number_format = "c">
+${100000.5}
+<#setting number_format = ",000.##">
+${100000.5}
+<@assertEquals expected="1" actual=double?c />
+<@assertEquals expected="1.000000000000001" actual=double2?c />
+<@assertEquals expected=(iciIntValue gte 2003032)?then("1E-16", "0.0000000000000001") actual=double3?c />
+<@assertEquals expected=(iciIntValue gte 2003032)?then("-1E-16", "-0.0000000000000001") actual=double4?c />
+<@assertEquals expected="1" actual=bigDecimal?c />
+<@assertEquals expected=(iciIntValue gte 2003032)?then("1E-16", "0.0000000000000001") actual=bigDecimal2?c />
 <#if iciIntValue gte 2003021>
   <@assertEquals expected="INF" actual="INF"?number?c />
   <@assertEquals expected="INF" actual="INF"?number?c />


[freemarker] 02/02: Typo/grammar fixes in messages and comments

Posted by dd...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git

commit 50825718699b40b1bd5963658d57f122ed3d56a8
Author: ddekany <dd...@apache.org>
AuthorDate: Fri Dec 16 12:36:12 2022 +0100

    Typo/grammar fixes in messages and comments
---
 src/main/java/freemarker/core/ArithmeticEngine.java                  | 2 +-
 src/main/java/freemarker/core/TemplateNumberFormat.java              | 5 ++---
 src/main/java/freemarker/core/_ArrayEnumeration.java                 | 2 +-
 src/main/java/freemarker/core/_ArrayIterator.java                    | 2 +-
 src/main/java/freemarker/core/_DelayedAOrAn.java                     | 2 +-
 src/main/java/freemarker/core/_DelayedConversionToString.java        | 2 +-
 src/main/java/freemarker/core/_DelayedFTLTypeDescription.java        | 2 +-
 src/main/java/freemarker/core/_DelayedGetCanonicalForm.java          | 2 +-
 src/main/java/freemarker/core/_DelayedGetMessage.java                | 2 +-
 src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java | 2 +-
 src/main/java/freemarker/core/_DelayedJQuote.java                    | 2 +-
 src/main/java/freemarker/core/_DelayedJoinWithComma.java             | 2 +-
 src/main/java/freemarker/core/_ErrorDescriptionBuilder.java          | 2 +-
 src/main/java/freemarker/core/_Java8.java                            | 2 +-
 src/main/java/freemarker/core/_Java8Impl.java                        | 2 +-
 src/main/java/freemarker/core/_JavaVersions.java                     | 2 +-
 src/main/java/freemarker/core/_MessageUtil.java                      | 2 +-
 .../freemarker/core/_ObjectBuilderSettingEvaluationException.java    | 2 +-
 src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java    | 4 ++--
 src/main/java/freemarker/core/_SettingEvaluationEnvironment.java     | 2 +-
 src/main/java/freemarker/core/_SortedArraySet.java                   | 2 +-
 .../freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java  | 2 +-
 src/main/java/freemarker/core/_UnmodifiableCompositeSet.java         | 2 +-
 src/main/java/freemarker/core/_UnmodifiableSet.java                  | 2 +-
 src/main/java/freemarker/template/Configuration.java                 | 2 +-
 25 files changed, 27 insertions(+), 28 deletions(-)

diff --git a/src/main/java/freemarker/core/ArithmeticEngine.java b/src/main/java/freemarker/core/ArithmeticEngine.java
index 5492b7ad..e6be82b0 100644
--- a/src/main/java/freemarker/core/ArithmeticEngine.java
+++ b/src/main/java/freemarker/core/ArithmeticEngine.java
@@ -126,7 +126,7 @@ public abstract class ArithmeticEngine {
                 return 0;
             } else {
                 // 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
+                // with lossless 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:
diff --git a/src/main/java/freemarker/core/TemplateNumberFormat.java b/src/main/java/freemarker/core/TemplateNumberFormat.java
index 23325755..10ca968c 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -83,8 +83,7 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
      * will be support, it will be similar to {@link TemplateDateFormat#parse(String, int)}.
      */
     public final Object parse(String s) throws TemplateValueFormatException {
-        throw new ParsingNotSupportedException("Number formats currenly don't support parsing");
+        throw new ParsingNotSupportedException("Number formats currently don't support parsing");
     }
-    
-    
+
 }
diff --git a/src/main/java/freemarker/core/_ArrayEnumeration.java b/src/main/java/freemarker/core/_ArrayEnumeration.java
index 70d7983d..72212a91 100644
--- a/src/main/java/freemarker/core/_ArrayEnumeration.java
+++ b/src/main/java/freemarker/core/_ArrayEnumeration.java
@@ -22,7 +22,7 @@ package freemarker.core;
 import java.util.Enumeration;
 import java.util.NoSuchElementException;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _ArrayEnumeration implements Enumeration {
 
     private final Object[] array;
diff --git a/src/main/java/freemarker/core/_ArrayIterator.java b/src/main/java/freemarker/core/_ArrayIterator.java
index f7cca25b..465ab21a 100644
--- a/src/main/java/freemarker/core/_ArrayIterator.java
+++ b/src/main/java/freemarker/core/_ArrayIterator.java
@@ -22,7 +22,7 @@ package freemarker.core;
 import java.util.Iterator;
 import java.util.NoSuchElementException;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _ArrayIterator implements Iterator {
 
     private final Object[] array;
diff --git a/src/main/java/freemarker/core/_DelayedAOrAn.java b/src/main/java/freemarker/core/_DelayedAOrAn.java
index 8d7306ee..2ed03529 100644
--- a/src/main/java/freemarker/core/_DelayedAOrAn.java
+++ b/src/main/java/freemarker/core/_DelayedAOrAn.java
@@ -19,7 +19,7 @@
 
 package freemarker.core;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _DelayedAOrAn extends _DelayedConversionToString {
 
     public _DelayedAOrAn(Object object) {
diff --git a/src/main/java/freemarker/core/_DelayedConversionToString.java b/src/main/java/freemarker/core/_DelayedConversionToString.java
index a705c010..65c9fae2 100644
--- a/src/main/java/freemarker/core/_DelayedConversionToString.java
+++ b/src/main/java/freemarker/core/_DelayedConversionToString.java
@@ -19,7 +19,7 @@
 
 package freemarker.core;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public abstract class _DelayedConversionToString {
 
     private static final String NOT_SET = new String();
diff --git a/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java b/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java
index 93d87137..90f2807c 100644
--- a/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java
+++ b/src/main/java/freemarker/core/_DelayedFTLTypeDescription.java
@@ -22,7 +22,7 @@ package freemarker.core;
 import freemarker.template.TemplateModel;
 import freemarker.template.utility.ClassUtil;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _DelayedFTLTypeDescription extends _DelayedConversionToString {
     
     public _DelayedFTLTypeDescription(TemplateModel tm) {
diff --git a/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java b/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java
index 2f070f5f..2637766e 100644
--- a/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java
+++ b/src/main/java/freemarker/core/_DelayedGetCanonicalForm.java
@@ -20,7 +20,7 @@
 package freemarker.core;
 
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _DelayedGetCanonicalForm extends _DelayedConversionToString {
     
     public _DelayedGetCanonicalForm(TemplateObject obj) {
diff --git a/src/main/java/freemarker/core/_DelayedGetMessage.java b/src/main/java/freemarker/core/_DelayedGetMessage.java
index ed2fe567..3254396b 100644
--- a/src/main/java/freemarker/core/_DelayedGetMessage.java
+++ b/src/main/java/freemarker/core/_DelayedGetMessage.java
@@ -19,7 +19,7 @@
 
 package freemarker.core;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _DelayedGetMessage extends _DelayedConversionToString {
 
     public _DelayedGetMessage(Throwable exception) {
diff --git a/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java b/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java
index 56111d09..e3e415af 100644
--- a/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java
+++ b/src/main/java/freemarker/core/_DelayedGetMessageWithoutStackTop.java
@@ -21,7 +21,7 @@ package freemarker.core;
 
 import freemarker.template.TemplateException;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _DelayedGetMessageWithoutStackTop extends _DelayedConversionToString {
 
     public _DelayedGetMessageWithoutStackTop(TemplateException exception) {
diff --git a/src/main/java/freemarker/core/_DelayedJQuote.java b/src/main/java/freemarker/core/_DelayedJQuote.java
index 7dfef58d..48bfaedc 100644
--- a/src/main/java/freemarker/core/_DelayedJQuote.java
+++ b/src/main/java/freemarker/core/_DelayedJQuote.java
@@ -21,7 +21,7 @@ package freemarker.core;
 
 import freemarker.template.utility.StringUtil;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _DelayedJQuote extends _DelayedConversionToString {
 
     public _DelayedJQuote(Object object) {
diff --git a/src/main/java/freemarker/core/_DelayedJoinWithComma.java b/src/main/java/freemarker/core/_DelayedJoinWithComma.java
index 68ce8b9b..17a64a2c 100644
--- a/src/main/java/freemarker/core/_DelayedJoinWithComma.java
+++ b/src/main/java/freemarker/core/_DelayedJoinWithComma.java
@@ -19,7 +19,7 @@
 
 package freemarker.core;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _DelayedJoinWithComma extends _DelayedConversionToString {
 
     public _DelayedJoinWithComma(String[] items) {
diff --git a/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java b/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java
index 8c7563d0..2b570d84 100644
--- a/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java
+++ b/src/main/java/freemarker/core/_ErrorDescriptionBuilder.java
@@ -31,7 +31,7 @@ import freemarker.template.utility.ClassUtil;
 import freemarker.template.utility.StringUtil;
 
 /**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
  * Packs a structured from of the error description from which the error message can be rendered on-demand.
  * Note that this class isn't serializable, thus the containing exception should render the message before it's
  * serialized.
diff --git a/src/main/java/freemarker/core/_Java8.java b/src/main/java/freemarker/core/_Java8.java
index 34979da8..e1a39543 100644
--- a/src/main/java/freemarker/core/_Java8.java
+++ b/src/main/java/freemarker/core/_Java8.java
@@ -21,7 +21,7 @@ package freemarker.core;
 import java.lang.reflect.Method;
 
 /**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
  * Used for accessing functionality that's only present in Java 8 or later.
  */
 public interface _Java8 {
diff --git a/src/main/java/freemarker/core/_Java8Impl.java b/src/main/java/freemarker/core/_Java8Impl.java
index a25e0f31..e4d14c07 100644
--- a/src/main/java/freemarker/core/_Java8Impl.java
+++ b/src/main/java/freemarker/core/_Java8Impl.java
@@ -21,7 +21,7 @@ package freemarker.core;
 import java.lang.reflect.Method;
 
 /**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
  * Used for accessing functionality that's only present in Java 8 or later.
  */
 // Compile this against Java 8
diff --git a/src/main/java/freemarker/core/_JavaVersions.java b/src/main/java/freemarker/core/_JavaVersions.java
index 3f076732..5670c369 100644
--- a/src/main/java/freemarker/core/_JavaVersions.java
+++ b/src/main/java/freemarker/core/_JavaVersions.java
@@ -23,7 +23,7 @@ import freemarker.template.Version;
 import freemarker.template.utility.SecurityUtilities;
 
 /**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
  */
 public final class _JavaVersions {
     
diff --git a/src/main/java/freemarker/core/_MessageUtil.java b/src/main/java/freemarker/core/_MessageUtil.java
index 081835cc..69ce60c6 100644
--- a/src/main/java/freemarker/core/_MessageUtil.java
+++ b/src/main/java/freemarker/core/_MessageUtil.java
@@ -30,7 +30,7 @@ import freemarker.template.TemplateModelException;
 import freemarker.template.utility.StringUtil;
 
 /**
- * Used internally only, might changes without notice!
+ * Used internally only, might change without notice!
  * Utilities for creating error messages (and other messages).
  */
 public class _MessageUtil {
diff --git a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java
index e262721b..cc62b0a0 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluationException.java
@@ -22,7 +22,7 @@ package freemarker.core;
 import freemarker.template.utility.StringUtil;
 
 /**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
  * Thrown by {@link _ObjectBuilderSettingEvaluator}.
  */
 public class _ObjectBuilderSettingEvaluationException extends Exception {
diff --git a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
index 7e4e18b2..0e89e10f 100644
--- a/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
+++ b/src/main/java/freemarker/core/_ObjectBuilderSettingEvaluator.java
@@ -59,7 +59,7 @@ import freemarker.template.utility.StringUtil;
 import freemarker.template.utility.WriteProtectable;
 
 /**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
  * 
  * Evaluates object builder expressions used in configuration {@link Properties}.
  * It should be replaced with FTL later (when it was improved to be practical for this), so the syntax should be
@@ -448,7 +448,7 @@ public class _ObjectBuilderSettingEvaluator {
                         }
                     } else {
                         if (resultCoerced) {
-                            // The FTL way (BigDecimal is loseless, and it will be coerced to the target type later):
+                            // The FTL way (BigDecimal is lossless, and it will be coerced to the target type later):
                             return new BigDecimal(numStr);
                         } else {
                             // The Java way (lossy but familiar):
diff --git a/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java b/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java
index 88a4abf3..f5700216 100644
--- a/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java
+++ b/src/main/java/freemarker/core/_SettingEvaluationEnvironment.java
@@ -25,7 +25,7 @@ import freemarker.ext.beans.BeansWrapper;
 import freemarker.template.Configuration;
 
 /**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
  * The runtime environment used during the evaluation of configuration {@link Properties}.
  */
 public class _SettingEvaluationEnvironment {
diff --git a/src/main/java/freemarker/core/_SortedArraySet.java b/src/main/java/freemarker/core/_SortedArraySet.java
index 4016eb8a..3cd2fe33 100644
--- a/src/main/java/freemarker/core/_SortedArraySet.java
+++ b/src/main/java/freemarker/core/_SortedArraySet.java
@@ -23,7 +23,7 @@ import java.util.Arrays;
 import java.util.Collection;
 import java.util.Iterator;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _SortedArraySet<E> extends _UnmodifiableSet<E> {
 
     private final E[] array;
diff --git a/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java b/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
index 18e8f07d..b370a7b7 100644
--- a/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
+++ b/src/main/java/freemarker/core/_UnexpectedTypeErrorExplainerTemplateModel.java
@@ -22,7 +22,7 @@ package freemarker.core;
 import freemarker.template.TemplateModel;
 
 /**
- * Don't use this; used internally by FreeMarker, might changes without notice.
+ * Don't use this; used internally by FreeMarker, might change without notice.
  * 
  * <p>Implemented by {@link TemplateModel}-s that can explain why they don't implement a certain type. 
  * */
diff --git a/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java b/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java
index e3b82abc..8d311294 100644
--- a/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java
+++ b/src/main/java/freemarker/core/_UnmodifiableCompositeSet.java
@@ -22,7 +22,7 @@ package freemarker.core;
 import java.util.Iterator;
 import java.util.Set;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public class _UnmodifiableCompositeSet<E> extends _UnmodifiableSet<E> {
     
     private final Set<E> set1, set2;
diff --git a/src/main/java/freemarker/core/_UnmodifiableSet.java b/src/main/java/freemarker/core/_UnmodifiableSet.java
index 8c984828..2e5ce690 100644
--- a/src/main/java/freemarker/core/_UnmodifiableSet.java
+++ b/src/main/java/freemarker/core/_UnmodifiableSet.java
@@ -21,7 +21,7 @@ package freemarker.core;
 
 import java.util.AbstractSet;
 
-/** Don't use this; used internally by FreeMarker, might changes without notice. */
+/** Don't use this; used internally by FreeMarker, might change without notice. */
 public abstract class _UnmodifiableSet<E> extends AbstractSet<E> {
 
     @Override
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 64d3b542..11ac38bd 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -3536,7 +3536,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     @Override
     protected String getCorrectedNameForUnknownSetting(String name) {
         if ("encoding".equals(name) || "charset".equals(name) || "default_charset".equals(name)) {
-            // [2.4] Default might changes to camel-case
+            // [2.4] Default might change to camel-case
             return DEFAULT_ENCODING_KEY;
         }
         if ("defaultCharset".equals(name)) {