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/30 23:26:37 UTC

[freemarker] 02/02: CFormat: Replace two Default23*CFormat classes with a single LegacyCFormat.

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 9e14cb0b55bd2ae266af0b6a702e9f8ef50eb6f8
Author: ddekany <dd...@apache.org>
AuthorDate: Fri Dec 30 23:47:52 2022 +0100

    CFormat: Replace two Default23*CFormat classes with a single LegacyCFormat.
---
 .../freemarker/core/AbstractJSONLikeFormat.java    |   3 +-
 .../freemarker/core/AbstractLegacyCFormat.java     | 104 -------------
 src/main/java/freemarker/core/Configurable.java    |   5 +-
 .../java/freemarker/core/Default230CFormat.java    |  73 ---------
 .../java/freemarker/core/Default2321CFormat.java   |  72 ---------
 src/main/java/freemarker/core/Environment.java     |  25 +--
 src/main/java/freemarker/core/JavaCFormat.java     |   4 +-
 src/main/java/freemarker/core/LegacyCFormat.java   | 173 +++++++++++++++++++++
 src/main/java/freemarker/core/XSCFormat.java       |   3 +-
 .../java/freemarker/core/_StandardCLanguages.java  |   3 +-
 .../java/freemarker/template/Configuration.java    |  16 +-
 src/manual/en_US/book.xml                          |  80 +++++-----
 src/test/java/freemarker/core/CustomCFormat.java   |   4 +-
 .../freemarker/template/ConfigurationTest.java     |  21 ++-
 14 files changed, 253 insertions(+), 333 deletions(-)

diff --git a/src/main/java/freemarker/core/AbstractJSONLikeFormat.java b/src/main/java/freemarker/core/AbstractJSONLikeFormat.java
index ad6b7349..7f305ff4 100644
--- a/src/main/java/freemarker/core/AbstractJSONLikeFormat.java
+++ b/src/main/java/freemarker/core/AbstractJSONLikeFormat.java
@@ -37,7 +37,8 @@ public abstract class AbstractJSONLikeFormat extends CFormat {
             "Infinity", "-Infinity", "NaN",
             "Infinity", "-Infinity", "NaN");
 
-    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE = (DecimalFormat) Default230CFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+            = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
     static {
         DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
         symbols.setInfinity("Infinity");
diff --git a/src/main/java/freemarker/core/AbstractLegacyCFormat.java b/src/main/java/freemarker/core/AbstractLegacyCFormat.java
deleted file mode 100644
index c564d144..00000000
--- a/src/main/java/freemarker/core/AbstractLegacyCFormat.java
+++ /dev/null
@@ -1,104 +0,0 @@
-/*
- * 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 freemarker.template.TemplateException;
-import freemarker.template.TemplateModelException;
-import freemarker.template.TemplateNumberModel;
-import freemarker.template.utility.StringUtil;
-import freemarker.template.utility.StringUtil.JsStringEncCompatibility;
-import freemarker.template.utility.StringUtil.JsStringEncQuotation;
-
-/**
- * Super class of {@link CFormat}-s that merely exist to mimic old {@code ?c} behavior for backward compatibility.
- *
- * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
- * constructor and most methods are not exposed outside FreeMarker, and so you can't create a custom implementation.
- * The class itself and some members are exposed as they are needed for configuring FreeMarker.
- *
- * @since 2.3.32
- * @see AbstractJSONLikeFormat
- */
-public abstract class AbstractLegacyCFormat extends CFormat {
-    // Visibility is not "protected" to avoid external implementations while this class is experimental.
-    AbstractLegacyCFormat() {
-    }
-
-    @Override
-    final String formatString(String s, Environment env) throws TemplateException {
-        return StringUtil.jsStringEnc(
-                s, JsStringEncCompatibility.JAVA_SCRIPT_OR_JSON, JsStringEncQuotation.QUOTATION_MARK);
-    }
-
-    @Override
-    final TemplateNumberFormat getTemplateNumberFormat(Environment env) {
-        return new LegacyCTemplateNumberFormat(env);
-    }
-
-    @Override
-    String getTrueString() {
-        return "true";
-    }
-
-    @Override
-    String getFalseString() {
-        return "false";
-    }
-
-    @Override
-    final String getNullString() {
-        return "null";
-    }
-
-    final class LegacyCTemplateNumberFormat extends JavaTemplateNumberFormat {
-
-        public LegacyCTemplateNumberFormat(Environment env) {
-            super(getLegacyNumberFormat(env), Environment.COMPUTER_FORMAT_STRING);
-        }
-
-        @Override
-        public String formatToPlainText(TemplateNumberModel numberModel) throws UnformattableValueException,
-                TemplateModelException {
-            Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
-            return format(number);
-        }
-
-        @Override
-        public boolean isLocaleBound() {
-            return false;
-        }
-
-        @Override
-        String format(Number number) throws UnformattableValueException {
-            if (number instanceof Integer || number instanceof Long) {
-                // Accelerate these fairly common cases
-                return number.toString();
-            }
-            return super.format(number);
-        }
-
-        @Override
-        public String getDescription() {
-            return "LegacyC(" + super.getDescription() + ")";
-        }
-
-    }
-
-}
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index bb6ef998..0e03b220 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -702,8 +702,7 @@ public class Configurable {
      * {@code "c"} {@link #setBooleanFormat(String) boolean_format}.
      *
      * <p>The default value depends on {@link Configuration#Configuration(Version) incompatible_improvements}.
-     * If that's 2.3.32 or higher, then it's {@code "JavaScript or JSON"}. For lower it's {@code "default 2.3.31"} or
-     * {@code "default 2.3.0"}.
+     * If that's 2.3.32 or higher, then it's {@code "JavaScript or JSON"}, otherwise it's {@code "legacy"}.
      *
      * @since 2.3.32
      */
@@ -2196,7 +2195,7 @@ public class Configurable {
      *       <br>String value: {@code "default"} (case insensitive) for the default (on {@link Configuration} only), or
      *       one of the predefined values {@code "JavaScript or JSON"}, {@code "JSON"},
      *       {@code "JavaScript"}, {@code "Java"}, {@code "XS"},
-     *       {@code "default 2.3.0"}, {@code "default 2.3.21"}, or
+     *       {@code "legacy"}, or
      *       {@code "default"} (only allowed for {@link Configuration} instances) for the default value,
      *       or an <a href="#fm_obe">object builder expression</a> that gives a {@link CFormat} object.
      *
diff --git a/src/main/java/freemarker/core/Default230CFormat.java b/src/main/java/freemarker/core/Default230CFormat.java
deleted file mode 100644
index 88749766..00000000
--- a/src/main/java/freemarker/core/Default230CFormat.java
+++ /dev/null
@@ -1,73 +0,0 @@
-/*
- * 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.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-import java.util.Locale;
-
-import freemarker.template.Configuration;
-import freemarker.template.Version;
-
-/**
- * Corresponds to the behavior of {@code ?c} if
- * {@link Configuration#Configuration(Version) incompatible_improvements} is less than
- * {@linkplain Configuration#VERSION_2_3_21 2.3.21}.
- * The only good reason for using this is strict backward-compatibility.
- *
- * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
- * constructor and most methods are not exposed outside FreeMarker, and so you can't create a custom implementation.
- * The class itself and some members are exposed as they are needed for configuring FreeMarker.
- *
- * @see Default2321CFormat
- * @see JSONCFormat
- *
- * @since 2.3.32
- */
-public final class Default230CFormat extends AbstractLegacyCFormat {
-    public static final Default230CFormat INSTANCE = new Default230CFormat();
-    public static final String NAME = "default 2.3.0";
-
-    /**
-     * "c" number format as it was before Incompatible Improvements 2.3.21.
-     */
-    static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE = new DecimalFormat(
-            "0.################",
-            new DecimalFormatSymbols(Locale.US));
-    static {
-        LEGACY_NUMBER_FORMAT_PROTOTYPE.setGroupingUsed(false);
-        LEGACY_NUMBER_FORMAT_PROTOTYPE.setDecimalSeparatorAlwaysShown(false);
-    }
-
-    private Default230CFormat() {
-    }
-
-    @Override
-    NumberFormat getLegacyNumberFormat(Environment env) {
-        // Note: DecimalFormat-s aren't thread-safe, so you must clone the static field value.
-        return (NumberFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-}
diff --git a/src/main/java/freemarker/core/Default2321CFormat.java b/src/main/java/freemarker/core/Default2321CFormat.java
deleted file mode 100644
index c466769a..00000000
--- a/src/main/java/freemarker/core/Default2321CFormat.java
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * 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.text.DecimalFormat;
-import java.text.DecimalFormatSymbols;
-import java.text.NumberFormat;
-
-import freemarker.template.Configuration;
-import freemarker.template.Version;
-
-/**
- * Corresponds to the behavior of {@code ?c} if
- * {@link Configuration#Configuration(Version) incompatible_improvements} is between
- * {@linkplain Configuration#VERSION_2_3_21 2.3.21} and {@linkplain Configuration#VERSION_2_3_31 2.3.31}.
- * The only good reason for using this is strict backward-compatibility.
- *
- * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
- * constructor and most methods are not exposed outside FreeMarker, and so you can't create a custom implementation.
- * The class itself and some members are exposed as they are needed for configuring FreeMarker.
- *
- * @see Default230CFormat
- * @see JSONCFormat
- *
- * @since 2.3.32
- */
-public final class Default2321CFormat extends AbstractLegacyCFormat {
-    public static final Default2321CFormat INSTANCE = new Default2321CFormat();
-    public static final String NAME = "default 2.3.21";
-
-    /**
-     * "c" number format as it was starting from Incompatible Improvements 2.3.21.
-     */
-    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE = (DecimalFormat) Default230CFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
-    static {
-        DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
-        symbols.setInfinity("INF");
-        symbols.setNaN("NaN");
-        LEGACY_NUMBER_FORMAT_PROTOTYPE.setDecimalFormatSymbols(symbols);
-    }
-
-    private Default2321CFormat() {
-    }
-
-    @Override
-    NumberFormat getLegacyNumberFormat(Environment env) {
-        // Note: DecimalFormat-s aren't thread-safe, so you must clone the static field value.
-        return (NumberFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
-    }
-
-    @Override
-    public String getName() {
-        return NAME;
-    }
-}
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index c5be9e17..a56d9ddf 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1679,7 +1679,13 @@ public final class Environment extends Configurable {
     @Deprecated
     public NumberFormat getCNumberFormat() {
         if (cNumberFormat == null) {
-            cNumberFormat = getCFormatWithPre2331IcIBug().getLegacyNumberFormat(this);
+            CFormat cFormat = getCFormat();
+            if (cFormat == LegacyCFormat.INSTANCE && configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_31) {
+                // Emulate old bug
+                cNumberFormat = ((LegacyCFormat) cFormat).getLegacyNumberFormat(_VersionInts.V_2_3_20);
+            } else {
+                cNumberFormat = cFormat.getLegacyNumberFormat(this);
+            }
         }
         return cNumberFormat;
     }
@@ -1705,20 +1711,17 @@ public final class Environment extends Configurable {
      */
     private TemplateNumberFormat getCTemplateNumberFormatWithPre2331IcIBug() {
         if (cTemplateNumberFormatWithPre2331IcIBug == null) {
-            cTemplateNumberFormatWithPre2331IcIBug = getCFormatWithPre2331IcIBug().getTemplateNumberFormat(this);
+            CFormat cFormat = getCFormat();
+            if (cFormat == LegacyCFormat.INSTANCE && configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_31) {
+                // Emulate old bug
+                cTemplateNumberFormatWithPre2331IcIBug = ((LegacyCFormat) cFormat).getTemplateNumberFormat(_VersionInts.V_2_3_20);
+            } else {
+                cTemplateNumberFormatWithPre2331IcIBug = cFormat.getTemplateNumberFormat(this);
+            }
         }
         return cTemplateNumberFormatWithPre2331IcIBug;
     }
 
-    private CFormat getCFormatWithPre2331IcIBug() {
-        CFormat cFormat = getCFormat();
-        if (cFormat == Default2321CFormat.INSTANCE
-                && configuration.getIncompatibleImprovements().intValue() < _VersionInts.V_2_3_31) {
-            return Default230CFormat.INSTANCE;
-        }
-        return cFormat;
-    }
-
     @Override
     public void setCFormat(CFormat cFormat) {
         CFormat prevCFormat = getCFormat();
diff --git a/src/main/java/freemarker/core/JavaCFormat.java b/src/main/java/freemarker/core/JavaCFormat.java
index 020378e0..64c6feb9 100644
--- a/src/main/java/freemarker/core/JavaCFormat.java
+++ b/src/main/java/freemarker/core/JavaCFormat.java
@@ -39,7 +39,9 @@ public final class JavaCFormat extends CFormat {
             "Double.POSITIVE_INFINITY", "Double.NEGATIVE_INFINITY", "Double.NaN",
             "Float.POSITIVE_INFINITY", "Float.NEGATIVE_INFINITY", "Float.NaN");
 
-    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE = (DecimalFormat) Default230CFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+            = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
+
     static {
         DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
         symbols.setInfinity("Double.POSITIVE_INFINITY");
diff --git a/src/main/java/freemarker/core/LegacyCFormat.java b/src/main/java/freemarker/core/LegacyCFormat.java
new file mode 100644
index 00000000..1f3d5886
--- /dev/null
+++ b/src/main/java/freemarker/core/LegacyCFormat.java
@@ -0,0 +1,173 @@
+/*
+ * 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.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.NumberFormat;
+import java.util.Locale;
+
+import freemarker.template.Configuration;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.Version;
+import freemarker.template._VersionInts;
+import freemarker.template.utility.StringUtil;
+
+/**
+ * Corresponds to the behavior of {@code ?c} before {@linkplain Configuration#VERSION_2_3_32 2.3.32} (when there
+ * were no {@link CFormat}-s yet). This only exists for strict backward-compatibility, otherwise avoid this, mostly
+ * because its number-to-string conversion sometimes do rounding, and infinity and NaN formatting has some glitches.
+ * This is the default of {@link Configurable#setCFormat(CFormat) c_format} if
+ * {@link Configuration#Configuration(Version) incompatible_improvements} is less than 2.3.32.
+ *
+ * <p>If {@link Configuration#Configuration(Version) incompatible_improvements} is at least
+ * 2.3.21, then infinity is formatted as {@code INF}, and NaN as
+ * {@code NaN}. If it's less, then infinity is formatted to the infinity character (U+221E), and NaN to the
+ * UNICODE replacement character (U+FFFD). But, because of an old bug that we emulate, this only applies to the behavior
+ * of {@code ?c}/{@code ?cn}, and not to the {@code "c"} and {@code "computer"}
+ * {@link Configurable#setNumberFormat(String) number_format}.
+ * The last uses the pre-2.3.21 format before {@link Configuration#Configuration(Version) incompatible_improvements}
+ * 2.3.31.
+ *
+ * <p><b>Experimental class!</b> This class is too new, and might will change over time. Therefore, for now
+ * constructor and most methods are not exposed outside FreeMarker, and so you can't create a custom implementation.
+ * The class itself and some members are exposed as they are needed for configuring FreeMarker.
+ *
+ * @see JavaScriptOrJSONCFormat
+ *
+ * @since 2.3.32
+ */
+public final class LegacyCFormat extends CFormat {
+    public static final LegacyCFormat INSTANCE = new LegacyCFormat();
+    public static final String NAME = "legacy";
+
+    /**
+     * "c" number format as it was before Incompatible Improvements 2.3.21.
+     */
+    static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0 = new DecimalFormat(
+            "0.################",
+            new DecimalFormatSymbols(Locale.US));
+    static {
+        LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.setGroupingUsed(false);
+        LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.setDecimalSeparatorAlwaysShown(false);
+    }
+
+    /**
+     * "c" number format as it was starting from Incompatible Improvements 2.3.21.
+     */
+    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21 = (DecimalFormat) LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
+    static {
+        DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21.getDecimalFormatSymbols();
+        symbols.setInfinity("INF");
+        symbols.setNaN("NaN");
+        LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21.setDecimalFormatSymbols(symbols);
+    }
+
+    private LegacyCFormat() {
+    }
+
+    @Override
+    final String formatString(String s, Environment env) throws TemplateException {
+        return StringUtil.jsStringEnc(
+                s, StringUtil.JsStringEncCompatibility.JAVA_SCRIPT_OR_JSON, StringUtil.JsStringEncQuotation.QUOTATION_MARK);
+    }
+
+    @Override
+    final TemplateNumberFormat getTemplateNumberFormat(Environment env) {
+        return getTemplateNumberFormat(env.getConfiguration().getIncompatibleImprovements().intValue());
+    }
+
+    TemplateNumberFormat getTemplateNumberFormat(int iciVersion) {
+        return new LegacyCTemplateNumberFormat(getLegacyNumberFormat(iciVersion));
+    }
+
+    @Override
+    String getTrueString() {
+        return "true";
+    }
+
+    @Override
+    String getFalseString() {
+        return "false";
+    }
+
+    @Override
+    final String getNullString() {
+        return "null";
+    }
+
+    @Override
+    NumberFormat getLegacyNumberFormat(Environment env) {
+        // Note: DecimalFormat-s aren't thread-safe, so you must clone the static field value.
+        return getLegacyNumberFormat(env.getConfiguration().getIncompatibleImprovements().intValue());
+    }
+
+    NumberFormat getLegacyNumberFormat(int iciVersion) {
+        NumberFormat numberFormatPrototype;
+        if (iciVersion < _VersionInts.V_2_3_21) {
+            numberFormatPrototype = LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0;
+        } else {
+            numberFormatPrototype = LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_21;
+        }
+        return (NumberFormat) numberFormatPrototype.clone();
+    }
+
+    @Override
+    public String getName() {
+        return NAME;
+    }
+
+    static final class LegacyCTemplateNumberFormat extends JavaTemplateNumberFormat {
+
+        public LegacyCTemplateNumberFormat(NumberFormat numberFormat) {
+            super(numberFormat, Environment.COMPUTER_FORMAT_STRING);
+        }
+
+        @Override
+        public String formatToPlainText(TemplateNumberModel numberModel) throws UnformattableValueException,
+                TemplateModelException {
+            Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
+            return format(number);
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return false;
+        }
+
+        @Override
+        String format(Number number) throws UnformattableValueException {
+            if (number instanceof Integer || number instanceof Long) {
+                // Accelerate these fairly common cases
+                return number.toString();
+            }
+            return super.format(number);
+        }
+
+        @Override
+        public String getDescription() {
+            return "LegacyC(" + super.getDescription() + ")";
+        }
+
+    }
+
+}
diff --git a/src/main/java/freemarker/core/XSCFormat.java b/src/main/java/freemarker/core/XSCFormat.java
index 016f8f27..49c4165b 100644
--- a/src/main/java/freemarker/core/XSCFormat.java
+++ b/src/main/java/freemarker/core/XSCFormat.java
@@ -47,7 +47,8 @@ public final class XSCFormat extends CFormat {
             "INF", "-INF", "NaN",
             "INF", "-INF", "NaN");
 
-    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE = (DecimalFormat) Default230CFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+            = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
     static {
         DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
         symbols.setInfinity("INF");
diff --git a/src/main/java/freemarker/core/_StandardCLanguages.java b/src/main/java/freemarker/core/_StandardCLanguages.java
index 9dec5a1b..9a22e7ca 100644
--- a/src/main/java/freemarker/core/_StandardCLanguages.java
+++ b/src/main/java/freemarker/core/_StandardCLanguages.java
@@ -33,8 +33,7 @@ final class StandardCFormats {
         addStandardCFormat(JavaScriptCFormat.INSTANCE);
         addStandardCFormat(JavaCFormat.INSTANCE);
         addStandardCFormat(XSCFormat.INSTANCE);
-        addStandardCFormat(Default230CFormat.INSTANCE);
-        addStandardCFormat(Default2321CFormat.INSTANCE);
+        addStandardCFormat(LegacyCFormat.INSTANCE);
     }
 
     private static void addStandardCFormat(CFormat cFormat) {
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 540294bd..7d4933c4 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -63,13 +63,12 @@ import freemarker.core.CFormat;
 import freemarker.core.CSSOutputFormat;
 import freemarker.core.CombinedMarkupOutputFormat;
 import freemarker.core.Configurable;
-import freemarker.core.Default230CFormat;
-import freemarker.core.Default2321CFormat;
 import freemarker.core.Environment;
 import freemarker.core.HTMLOutputFormat;
 import freemarker.core.JSONOutputFormat;
 import freemarker.core.JavaScriptOrJSONCFormat;
 import freemarker.core.JavaScriptOutputFormat;
+import freemarker.core.LegacyCFormat;
 import freemarker.core.MarkupOutputFormat;
 import freemarker.core.OutputFormat;
 import freemarker.core.ParseException;
@@ -959,8 +958,7 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *         {@code "computer"} {@link Configurable#setNumberFormat(String) number_format}) changes, if the
      *         {@link #setCFormat(CFormat) c_format} setting was left on its default. The default of
      *         {@link #setCFormat(CFormat) c_format} changes to {@link JavaScriptOrJSONCFormat#INSTANCE}, from
-     *         {@link Default2321CFormat#INSTANCE} (or from {@link Default230CFormat#INSTANCE}, depending on the
-     *         previous Incompatible Improvement value), and that's what contains the changes:</p>
+     *         {@link LegacyCFormat#INSTANCE}, and that's what contains the changes:</p>
      *         <ul>
      *           <li><p>Changes affecting non-whole numbers, and whole numbers with over 100 digits:
      *             Formatting is now lossless, so it potentially shows much more decimals.
@@ -2512,13 +2510,9 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
     }
 
     static CFormat getDefaultCFormat(Version incompatibleImprovements) {
-        if (incompatibleImprovements.intValue() >= _VersionInts.V_2_3_32) {
-            return JavaScriptOrJSONCFormat.INSTANCE;
-        }
-        if (incompatibleImprovements.intValue() >= _VersionInts.V_2_3_21) {
-            return Default2321CFormat.INSTANCE;
-        }
-        return Default230CFormat.INSTANCE;
+        return incompatibleImprovements.intValue() >= _VersionInts.V_2_3_32
+                ? JavaScriptOrJSONCFormat.INSTANCE
+                : LegacyCFormat.INSTANCE;
     }
 
     /**
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 06f82070..2f9d3b7d 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -6120,17 +6120,11 @@ To prove that "s" didn't contain the value in escaped form:
           </listitem>
 
           <listitem>
-            <para><literal>"default 2.3.0"</literal>: Default for backward
-            compatibility if the <link
+            <para><literal>"legacy"</literal>: Default for backward
+            compatibility when the <link
             linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
-            setting</link> is less than 2.3.21. Avoid!</para>
-          </listitem>
-
-          <listitem>
-            <para><literal>"default 2.3.21"</literal>: Default for backward
-            compatibility if the <link
-            linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
-            setting</link> is equal or greater than 2.3.21. Avoid!</para>
+            setting</link> is less than 2.3.32. Avoid! Can have rounding
+            losses, and formatting glitches for infinity and NaN.</para>
           </listitem>
         </itemizedlist>
 
@@ -13494,12 +13488,11 @@ GreEN mouse
 
           <itemizedlist>
             <listitem>
-              <para><quote>JSON</quote>, <quote>default 2.3.0</quote>, and
-              <quote>default 2.3.21</quote>: Gives a JSON string literal, that
-              is, it will be surrounded with quotation marks
-              (<literal>"</literal>), and will be escaped using backslash
-              (<literal>\</literal>) where needed. For the exact escaping
-              rules see <link linkend="ref_builtin_json_string">the
+              <para><quote>JSON</quote>, <quote>legacy</quote>: Gives a JSON
+              string literal, that is, it will be surrounded with quotation
+              marks (<literal>"</literal>), and will be escaped using
+              backslash (<literal>\</literal>) where needed. For the exact
+              escaping rules see <link linkend="ref_builtin_json_string">the
               <literal>json_string</literal> built-in</link>.</para>
             </listitem>
 
@@ -16036,18 +16029,18 @@ rif: foo XYr baar</programlisting>
 
               <itemizedlist>
                 <listitem>
-                  <para>For all non-deprecated <literal>c_format</literal>-s
-                  that are built into FreeMarker: There's no rounding.</para>
+                  <para>For all that are built into FreeMarker, except
+                  <quote>legacy</quote> <literal>c_format </literal>: There's
+                  no rounding.</para>
                 </listitem>
 
                 <listitem>
-                  <para>For the deprecated <literal>c_format</literal>-s,
-                  <quote>default 2.3.0</quote>, and <quote>default
-                  2.3.21</quote>: The numbers are limited to 16 digits after
-                  the decimal dot, so rounding can occur. The these formats
-                  never use exponential form either, so the decimal point
-                  place is fixed. Thus, for example, 1E-17 will be formatted
-                  as <literal>0</literal>. </para>
+                  <para>For the deprecated <literal>c_format</literal>,
+                  <quote>legacy</quote>: The numbers are limited to 16 digits
+                  after the decimal dot, so rounding can occur. The these
+                  formats never use exponential form either, so the decimal
+                  point place is fixed. Thus, for example, 1E-17 will be
+                  formatted as <literal>0</literal>.</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -16079,14 +16072,19 @@ rif: foo XYr baar</programlisting>
 
                 <listitem>
                   <para>For <literal>c_format</literal> <quote>XS</quote>, and
-                  also for the deprecated <literal>c_format</literal>,
-                  <quote>default 2.3.21</quote>: <literal>INF</literal>,
-                  <literal>-INF</literal>, and <literal>NaN</literal>.</para>
+                  also if <link
+                  linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
+                  setting</link> is at least 2.3.21, for
+                  <literal>c_format</literal>, <quote>legacy</quote>:
+                  <literal>INF</literal>, <literal>-INF</literal>, and
+                  <literal>NaN</literal>.</para>
                 </listitem>
 
                 <listitem>
-                  <para>For the deprecated <literal>c_format</literal>,
-                  <quote>default 2.3.0</quote>: Gives what
+                  <para>For <literal>c_format</literal> <quote>legacy</quote>,
+                  if <link
+                  linkend="pgui_config_incompatible_improvements"><literal>incompatible_improvements</literal>
+                  setting</link> is less than 2.3.21: Gives what
                   <literal>java.text.DecimalFormat</literal> does with US
                   locale, which are <literal>∞</literal>,
                   <literal>-∞</literal>, and <literal>�</literal> (U+FFFD,
@@ -16096,10 +16094,9 @@ rif: foo XYr baar</programlisting>
             </listitem>
 
             <listitem>
-              <para>Exponential form is used by all non-deprecated
-              <literal>c_format</literal>-s that are built into FreeMarker
-              (but not by the deprecated <quote>default 2.3.0</quote>, and
-              <quote>default 2.3.21</quote>):</para>
+              <para>Exponential form is used by all
+              <literal>c_format</literal>-s that are built into FreeMarker,
+              except <quote>legacy</quote>:</para>
 
               <itemizedlist>
                 <listitem>
@@ -16231,8 +16228,8 @@ rif: foo XYr baar</programlisting>
           <itemizedlist>
             <listitem>
               <para>For <quote>JSON</quote>, <quote>Java</quote>,
-              <quote>JavaScript</quote>, <quote>default 2.3.0</quote>,
-              <quote>default 2.3.21</quote>: <literal>null</literal></para>
+              <quote>JavaScript</quote>, and <quote>legacy</quote>:
+              <literal>null</literal></para>
             </listitem>
 
             <listitem>
@@ -30075,12 +30072,13 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   <para>If you set the <link
                   linkend="pgui_config_incompatible_improvements_how_to_set"><literal>incompatible_improvements</literal>
                   setting</link> to 2.3.32, the default of
-                  <literal>c_format</literal> changes to
-                  <literal>JSON</literal>, which is a format that most targets
-                  can parse (because we just format simple values, not lists
-                  and maps). With lower
+                  <literal>c_format</literal> changes from
+                  <literal>"legacy"</literal> to <literal>"JavaScript or
+                  JSON"</literal>, which is a format that most targets can
+                  parse (because we just format simple values, not lists and
+                  maps). With lower
                   <literal>incompatible_improvements</literal>, the default
-                  value is a <literal>c_format</literal> that emulates the old
+                  value is <literal>"legacy"</literal> that emulates the old
                   behavior of <literal>?c</literal> (where you can lose
                   numerical prevision, etc.), so it's recommended to set it to
                   something else.</para>
diff --git a/src/test/java/freemarker/core/CustomCFormat.java b/src/test/java/freemarker/core/CustomCFormat.java
index 86658b0d..9ec224b8 100644
--- a/src/test/java/freemarker/core/CustomCFormat.java
+++ b/src/test/java/freemarker/core/CustomCFormat.java
@@ -36,8 +36,8 @@ class CustomCFormat extends CFormat {
             "M:INF", "M:NINF", "M:NaN",
             "M:INF", "M:NINF", "M:NaN");
 
-    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE =
-            (DecimalFormat) Default230CFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE.clone();
+    private static final DecimalFormat LEGACY_NUMBER_FORMAT_PROTOTYPE
+            = (DecimalFormat) LegacyCFormat.LEGACY_NUMBER_FORMAT_PROTOTYPE_2_3_0.clone();
 
     static {
         DecimalFormatSymbols symbols = LEGACY_NUMBER_FORMAT_PROTOTYPE.getDecimalFormatSymbols();
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 3a6dea5b..756b3fae 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -61,8 +61,6 @@ import freemarker.core.Configurable.SettingValueAssignmentException;
 import freemarker.core.Configurable.UnknownSettingException;
 import freemarker.core.ConfigurableTest;
 import freemarker.core.CustomHTMLOutputFormat;
-import freemarker.core.Default230CFormat;
-import freemarker.core.Default2321CFormat;
 import freemarker.core.DefaultTruncateBuiltinAlgorithm;
 import freemarker.core.DummyOutputFormat;
 import freemarker.core.Environment;
@@ -74,6 +72,7 @@ import freemarker.core.JSONCFormat;
 import freemarker.core.JavaCFormat;
 import freemarker.core.JavaScriptCFormat;
 import freemarker.core.JavaScriptOrJSONCFormat;
+import freemarker.core.LegacyCFormat;
 import freemarker.core.MarkupOutputFormat;
 import freemarker.core.OptInTemplateClassResolver;
 import freemarker.core.OutputFormat;
@@ -190,13 +189,13 @@ public class ConfigurationTest extends TestCase {
         assertFalse(((DefaultObjectWrapper) cfg.getObjectWrapper()).getPreferIndexedReadMethod());
 
         cfg = new Configuration(Configuration.VERSION_2_3_0);
-        assertSame(Default230CFormat.INSTANCE, cfg.getCFormat());
+        assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_20);
-        assertSame(Default230CFormat.INSTANCE, cfg.getCFormat());
+        assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_21);
-        assertSame(Default2321CFormat.INSTANCE, cfg.getCFormat());
+        assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_31);
-        assertSame(Default2321CFormat.INSTANCE, cfg.getCFormat());
+        assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_32);
         assertSame(JavaScriptOrJSONCFormat.INSTANCE, cfg.getCFormat());
         cfg.setCFormat(JavaScriptOrJSONCFormat.INSTANCE); // Same as default, but explicitly set now
@@ -1921,17 +1920,17 @@ public class ConfigurationTest extends TestCase {
     public void testCFormat() throws TemplateException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_21);
 
-        assertSame(Default2321CFormat.INSTANCE, cfg.getCFormat());
-        cfg.setSetting(Configuration.C_FORMAT_KEY_SNAKE_CASE, Default230CFormat.NAME);
-        assertSame(Default230CFormat.INSTANCE, cfg.getCFormat());
+        assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
+        cfg.setSetting(Configuration.C_FORMAT_KEY_SNAKE_CASE, LegacyCFormat.NAME);
+        assertSame(LegacyCFormat.INSTANCE, cfg.getCFormat());
         cfg.setSetting(Configuration.C_FORMAT_KEY_CAMEL_CASE, JSONCFormat.NAME);
         assertSame(JSONCFormat.INSTANCE, cfg.getCFormat());
 
         cfg.setSetting(Configuration.C_FORMAT_KEY_CAMEL_CASE, "default");
-        cfg.setSetting(Configuration.C_FORMAT_KEY_SNAKE_CASE, Default2321CFormat.NAME);
+        cfg.setSetting(Configuration.C_FORMAT_KEY_SNAKE_CASE, LegacyCFormat.NAME);
 
         for (CFormat standardCFormat : new CFormat[] {
-                        Default230CFormat.INSTANCE, Default2321CFormat.INSTANCE,
+                        LegacyCFormat.INSTANCE,
                         JSONCFormat.INSTANCE, JavaScriptCFormat.INSTANCE, JavaCFormat.INSTANCE,
                         XSCFormat.INSTANCE
                 }) {