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 2015/09/18 20:16:49 UTC

incubator-freemarker git commit: In number/date/time/datetime format strings, initial @ can be used to refer to custom formats even if incompatible_improvements is less than 2.3.24, as far as there's any custom format defined. As custom formats is also a

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3-gae ee7f23f7f -> d5bfedbad


In number/date/time/datetime format strings, initial @ can be used to refer to custom formats even if incompatible_improvements is less than 2.3.24, as far as there's any custom format defined. As custom formats is also a 2.3.24 feature, it's backward compatible this way.


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

Branch: refs/heads/2.3-gae
Commit: d5bfedbad0169fbf26ba34c0b43167d433f22cc0
Parents: ee7f23f
Author: ddekany <dd...@apache.org>
Authored: Fri Sep 18 20:13:35 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Sep 18 20:13:35 2015 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java | 47 +++++++++----
 src/main/java/freemarker/core/Environment.java  |  8 +--
 .../java/freemarker/template/Configuration.java | 14 +++-
 src/manual/book.xml                             | 72 +++++++++++---------
 .../java/freemarker/core/DateFormatTest.java    | 24 +++++--
 .../java/freemarker/core/NumberFormatTest.java  | 22 ++++--
 .../freemarker/template/ConfigurationTest.java  | 65 ++++++++++++++++++
 7 files changed, 194 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 44e3fcb..bdc76ae 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -726,12 +726,12 @@ public class Configurable {
      *       rounding mode, and {@code _} as the group separator. See more about "extended Java decimal format" in the
      *       FreeMarker Manual.
      *       </li>
-     *   <li>If the string starts with {@code @} character, and
-     *       {@link Configuration#setIncompatibleImprovements(Version)} is at least 2.3.24, then it's interpreted as a
-     *       custom number format. The format of such string is <code>"@<i>name</i>"</code> or
-     *       <code>"@<i>name</i> <i>parameters</i>"</code>, where <code><i>name</i></code> is the key in the
-     *       {@link Map} set by {@link #setCustomNumberFormats(Map)}, and <code><i>parameters</i></code> is parsed by
-     *       the custom {@link TemplateNumberFormat}.
+     *   <li>If the string starts with {@code @} character then it's interpreted as a custom number format, but only if
+     *       either {@link Configuration#getIncompatibleImprovements()} is at least 2.3.24, or there's any custom
+     *       formats defined (even if custom date/time/dateTime format). The format of a such string is
+     *       <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomNumberFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom {@link TemplateNumberFormat}.
      *   </li>
      * </ul>
      * 
@@ -771,7 +771,11 @@ public class Configurable {
     
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setNumberFormat(String)
-     * number_format} setting with values starting with <code>@<i>name</i></code>.
+     * number_format} setting with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} will have special meaning in number/date/time/datetime format strings, even if
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less than 2.3.24 (starting with
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24 {@code @} always has special
+     * meaning).
      * 
      * @param customNumberFormats
      *            Can't be {@code null}.
@@ -827,6 +831,17 @@ public class Configurable {
     }
     
     /**
+     * Tells if this configurable object or its parent defines any custom formats.
+     * 
+     * @since 2.3.24
+     */
+    public boolean hasCustomFormats() {
+        return customNumberFormats != null && !customNumberFormats.isEmpty()
+                || customDateFormats != null && !customDateFormats.isEmpty()
+                || getParent() != null && getParent().hasCustomFormats(); 
+    }
+    
+    /**
      * The string value for the boolean {@code true} and {@code false} values, intended for human audience (not for a
      * computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is significant,
      * so {@code "yes, no"} is WRONG (unless you want that leading space before "no").
@@ -1082,12 +1097,12 @@ public class Configurable {
      *       them with {@code _}, like {@code "short_medium"}. ({@code "medium"} means
      *       {@code "medium_medium"} for date-time values.)
      *       
-     *   <li><p>Anything that starts with {@code "@"}, but only if
-     *       {@link Configuration#setIncompatibleImprovements(Version)} is at least 2.3.24, is interpreted as a custom
-     *       date/time/dateTime format. The format of such string is <code>"@<i>name</i>"</code> or
-     *       <code>"@<i>name</i> <i>parameters</i>"</code>, where <code><i>name</i></code> is the key in the
-     *       {@link Map} set by {@link #setCustomDateFormats(Map)}, and <code><i>parameters</i></code> is parsed by the
-     *       custom number format.
+     *   <li><p>Anything that starts with {@code "@"} is interpreted as a custom
+     *       date/time/dateTime format, but only if either {@link Configuration#getIncompatibleImprovements()}
+     *       is at least 2.3.24, or there's any custom formats defined (even if custom number format). The format of
+     *       such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomDateFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom number format.
      *       
      * </ul> 
      * 
@@ -1127,7 +1142,11 @@ public class Configurable {
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setDateTimeFormat(String)
      * date_format}, {@link #setDateTimeFormat(String) time_format}, and {@link #setDateTimeFormat(String)
-     * datetime_format} settings with values starting with <code>@<i>name</i></code>.
+     * datetime_format} settings with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} will have special meaning in number/date/time/datetime format strings, even if
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less than 2.3.24 (starting with
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24 {@code @} always has special
+     * meaning).
      *
      * @param customDateFormats
      *            Can't be {@code null}.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/main/java/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index da980c7..96e31fd 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1236,7 +1236,7 @@ public final class Environment extends Configurable {
         if (formatStringLen > 1
                 && formatString.charAt(0) == '@'
                 && formatString.charAt(1) != '@'
-                && isIcI2324OrLater()) {
+                && (isIcI2324OrLater() || hasCustomFormats())) {
             final String name;
             final String params;
             {
@@ -1262,7 +1262,7 @@ public final class Environment extends Configurable {
             if (formatStringLen > 1
                     && formatString.charAt(0) == '@'
                     && formatString.charAt(1) == '@'
-                    && isIcI2324OrLater()) {
+                    && (isIcI2324OrLater() || hasCustomFormats())) {
                 // Unescape @ escaped as @@
                 formatString = formatString.substring(1);
             }
@@ -1743,7 +1743,7 @@ public final class Environment extends Configurable {
         } else if (firstChar == '@'
                 && formatStringLen > 1
                 && formatString.charAt(1) != '@'
-                && isIcI2324OrLater()) {
+                && (isIcI2324OrLater() || hasCustomFormats())) {
             final String name;
             {
                 int endIdx;
@@ -1767,7 +1767,7 @@ public final class Environment extends Configurable {
             if (firstChar == '@'
                     && formatStringLen > 1
                     && formatString.charAt(1) == '@'
-                    && isIcI2324OrLater()) {
+                    && (isIcI2324OrLater() || hasCustomFormats())) {
                 // Unescape @ escaped as @@
                 unescapedFormatString = formatString.substring(1);
             } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 4f0b015..fe178dc 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -740,9 +740,21 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *          These "file" extensions aren't case sensitive.
      *       </li>
      *       <li><p>
+     *          In number format and date format strings (like in the {@code number_format} setting, or in templates in
+     *          {@code n?string("0.##")}), an initial {@code '@'} has special meaning; they refer to a custom format
+     *          with the name given after the {@code @} (see: {@link #setCustomNumberFormats(Map)},
+     *          {@link #setCustomDateFormats(Map)}, {@link #setNumberFormat(String)}, and {@link #setDateTimeFormat}).
+     *          If the custom format doesn't exist, that will be an error. To have a literal {@code @} as the first
+     *          character in the output, it has to be written as {@code @@}. Again, all this only applies to the very
+     *          first character of the format string, so {@code @} characters elsewhere must not be doubled. Also, if
+     *          there are any custom formats defined, initial {@code '@'} will have the new meaning regardless of
+     *          the value of the {@code incompatible_improvements} setting. So you don't need to set the
+     *          {@code incompatible_improvements} only to use custom formats. 
+     *       </li>
+     *       <li><p>
      *          Expressions inside interpolations that were inside <em>string literal expressions</em>
      *          (not <code>${...}</code>-s in general), like in <code>&lt;#assign s="Hello ${name}!"&gt;</code>, has
-     *          always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect).
+     *          always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect). Now it's fixed.
      *       </li>
      *     </ul>
      *   </li>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 17417ec..af492e4 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -9155,13 +9155,10 @@ cfg.setTemplateConfigurers(
         formats can be registered with the
         <literal>custom_number_formats</literal> and
         <literal>custom_date_formats</literal> configuration settings. After
-        that, if you <link
-        linkend="pgui_config_incompatible_improvements_how_to_set">set the
-        <literal>incompatible_improvements</literal> setting</link> to 2.3.24
-        or higher, anywhere where you can specify formats with a
+        that, anywhere where you can specify formats with a
         <literal>String</literal>, now you can refer to your custom format as
         <literal>"@<replaceable>name</replaceable>"</literal>. So for example,
-        if you have registered a your number format implementation with name
+        if you have registered your number format implementation with name
         <literal>"smart"</literal>, then you could set the
         <literal>number_format</literal> setting
         (<literal>Configurable.setNumberFormat(String)</literal>) to
@@ -12335,8 +12332,8 @@ Green Mouse</programlisting>
           such returns exactly the same Java object that the date parser
           (<literal>freemarker.core.TemplateDateFormat</literal>
           implementation) returns, while <literal>?date</literal> without the
-          <literal>()</literal> returns a tricky wrapper that's a date and a
-          method and hash on the same time.</para>
+          <literal>()</literal> returns a tricky wrapper value that's a date
+          and a method and hash on the same time.</para>
         </section>
 
         <section xml:id="ref_builtin_ends_with">
@@ -20575,16 +20572,18 @@ ${"'{}"}
                 </listitem>
 
                 <listitem>
-                  <para>Values starting with <literal>@</literal>, if <link
+                  <para>Values starting with <literal>@</literal> refer to a
+                  <link linkend="pgui_config_custom_formats">custom
+                  format</link>, but only if either <link
                   linkend="pgui_config_incompatible_improvements_how_to_set">the
                   <literal>incompatible_improvements</literal> setting</link>
-                  is at least 2.3.24, refers to a <link
-                  linkend="pgui_config_custom_formats">custom format</link>.
-                  For example, <literal>"@price"</literal> refers to the
-                  custom format registered with the <literal>"price"</literal>
-                  name. The custom format name is possibly followed by space
-                  or <literal>_</literal> and then format parameters, whose
-                  interpretation depends on the custom format.</para>
+                  is at least 2.3.24, or there are some custom formats
+                  defined. For example, <literal>"@price"</literal> refers to
+                  the custom format registered with the
+                  <literal>"price"</literal> name. The custom format name is
+                  possibly followed by space or <literal>_</literal> and then
+                  format parameters, whose interpretation depends on the
+                  custom format.</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -20833,16 +20832,20 @@ ${"'{}"}
                 </listitem>
 
                 <listitem>
-                  <para>Values starting with <literal>@</literal>, if <link
+                  <para>Values starting with <literal>@</literal> refer to a
+                  <link linkend="pgui_config_custom_formats">custom
+                  format</link>, like <literal>"@departure"</literal> refers
+                  to the custom format registered with the
+                  <literal>"departure"</literal> name. The format name is
+                  possibly followed by space or <literal>_</literal> and then
+                  format parameters, whose interpretation depends on the
+                  custom format. For backward compatibility, the initial
+                  <literal>@</literal> only has this new meaning if either
+                  <link
                   linkend="pgui_config_incompatible_improvements_how_to_set">the
                   <literal>incompatible_improvements</literal> setting</link>
-                  is at least 2.3.24, refer to a <link
-                  linkend="pgui_config_custom_formats">custom format</link>,
-                  like <literal>"@departure"</literal> refers to the custom
-                  format registered with the <literal>"departure"</literal>
-                  name. The format name is possibly followed by space or
-                  <literal>_</literal> and then format parameters, whose
-                  interpretation depends on the custom format.</para>
+                  is at least 2.3.24, or there's any custom formats
+                  defined.</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -25751,14 +25754,12 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               <literal>${n?string.@foo_params}</literal>,
               <literal>&lt;#setting number_format='@foo params'&gt;</literal>,
               or <literal>${n?string.@foo}</literal>,
-              <literal>${n?string.@foo_params}</literal>. This means that
-              <replaceable>if</replaceable> custom formats are allowed in the
-              configuration (by <link
-              linkend="pgui_config_incompatible_improvements_how_to_set">setting
-              <literal>incompatible_improvements</literal></link> to 2.3.24),
-              an <replaceable>initial</replaceable> <literal>@</literal> in
-              format strings is now reserved, and need to be escaped as
-              <literal>@@</literal> if it has to be there literally.</para>
+              <literal>${n?string.@foo_params}</literal>. For backward
+              compatibility, the initial <literal>@</literal> only has this
+              special meaning if you have any custom formats or <link
+              linkend="pgui_config_incompatible_improvements">the
+              <literal>incompatible_improvements</literal> setting</link> is
+              2.3.24 or higher.</para>
             </listitem>
 
             <listitem>
@@ -26150,6 +26151,15 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>In number/date/time/datetime format strings, initial
+              <literal>@</literal> can be used to refer to custom formats even
+              if <literal>incompatible_improvements</literal> is less than
+              2.3.24, as far as there's any custom format defined. As custom
+              formats is also a 2.3.24 feature, it's backward compatible this
+              way.</para>
+            </listitem>
+
+            <listitem>
               <para><literal>?date</literal>, <literal>?time</literal> and
               <literal>?datetime</literal> now can be called as 0 argument
               method, like <literal>?date()</literal>, etc., which returns the

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/test/java/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/DateFormatTest.java b/src/test/java/freemarker/core/DateFormatTest.java
index af57f77..741e68b 100644
--- a/src/test/java/freemarker/core/DateFormatTest.java
+++ b/src/test/java/freemarker/core/DateFormatTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.sql.Time;
 import java.sql.Timestamp;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -196,16 +197,31 @@ public class DateFormatTest extends TemplateTest {
     public void testIcIAndEscaping() throws Exception {
         Configuration cfg = getConfiguration();
         addToDataModel("d", new SimpleDate(new Date(12345678L), TemplateDateModel.DATETIME));
-        cfg.setDateTimeFormat("@@yyyy");
-        assertOutput("${d}", "@1970");
-        cfg.setDateTimeFormat("@epoch");
-        assertOutput("${d}", "12345678");
+        
+        testIcIAndEscapingWhenCustFormsAreAccepted(cfg);
         
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_23);
+        testIcIAndEscapingWhenCustFormsAreAccepted(cfg);
+        
+        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        
         cfg.setDateTimeFormat("@@yyyy");
         assertOutput("${d}", "@@1970");
         cfg.setDateTimeFormat("@epoch");
         assertErrorContains("${d}", "\"@epoch\"");
+        
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_24);
+        cfg.setDateTimeFormat("@@yyyy");
+        assertOutput("${d}", "@1970");
+        cfg.setDateTimeFormat("@epoch");
+        assertErrorContains("${d}", "custom", "\"epoch\"");
+    }
+
+    protected void testIcIAndEscapingWhenCustFormsAreAccepted(Configuration cfg) throws IOException, TemplateException {
+        cfg.setDateTimeFormat("@@yyyy");
+        assertOutput("${d}", "@1970");
+        cfg.setDateTimeFormat("@epoch");
+        assertOutput("${d}", "12345678");
     }
     
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/test/java/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index e2fc2b2..78b420f 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Map;
 
@@ -208,16 +209,29 @@ public class NumberFormatTest extends TemplateTest {
     @Test
     public void testIcIAndEscaping() throws Exception {
         Configuration cfg = getConfiguration();
-        cfg.setNumberFormat("@@0");
-        assertOutput("${10}", "@10");
-        cfg.setNumberFormat("@hex");
-        assertOutput("${10}", "a");
+        testIcIAndEscapingWhenCustFormsAccepted(cfg);
         
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_23);
+        testIcIAndEscapingWhenCustFormsAccepted(cfg);
+        
+        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
         cfg.setNumberFormat("@@0");
         assertOutput("${10}", "@@10");
         cfg.setNumberFormat("@hex");
         assertOutput("${10}", "@hex10");
+        
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_24);
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@hex");
+        assertErrorContains("${10}", "custom", "\"hex\"");
+    }
+
+    protected void testIcIAndEscapingWhenCustFormsAccepted(Configuration cfg) throws IOException, TemplateException {
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@hex");
+        assertOutput("${10}", "a");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/test/java/freemarker/template/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 6d5964f..b7de38e 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -1450,6 +1450,71 @@ public class ConfigurationTest extends TestCase {
         }
     }
     
+    @Test
+    public void testHasCustomFormats() throws IOException, TemplateException {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
+        Template t = new Template(null, "", cfg);
+        Environment env = t.createProcessingEnvironment(null, null);
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        env.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertFalse(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        t.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertFalse(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        
+        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        
+        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        // Same with number formats:
+        
+        env.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertFalse(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        t.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertFalse(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        
+        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
+        t.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
+        env.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+    }
+    
     public void testNamingConventionSetSetting() throws TemplateException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);