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 2021/11/08 00:56:00 UTC

[freemarker] 02/03: [FREEMARKER-35] Added temporalSupport property to DefaultObjectWrapper, and BeansWrapper, which only defaults to true starting from incompatible_improvements 2.3.32.

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

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

commit d43c096cee175c9b252bd0fbc2bfe40f93063690
Author: ddekany <dd...@apache.org>
AuthorDate: Mon Nov 8 01:37:49 2021 +0100

    [FREEMARKER-35] Added temporalSupport property to DefaultObjectWrapper, and BeansWrapper, which only defaults to true starting from incompatible_improvements 2.3.32.
---
 .../java/freemarker/ext/beans/BeansModelCache.java |  5 ++
 .../java/freemarker/ext/beans/BeansWrapper.java    | 54 ++++++++++++++++---
 .../ext/beans/BeansWrapperConfiguration.java       | 15 ++++++
 .../java/freemarker/ext/beans/TemporalModel.java   | 61 ++++++++++++++++++++++
 .../java/freemarker/template/Configuration.java    |  9 ++++
 .../freemarker/template/DefaultObjectWrapper.java  |  7 ++-
 .../freemarker/template/utility/ClassUtil.java     |  3 ++
 .../freemarker/core/TemporalErrorMessagesTest.java |  7 ++-
 .../freemarker/ext/beans/BeansWrapperMiscTest.java | 31 ++++++++++-
 .../template/DefaultObjectWrapperTest.java         | 16 +++++-
 .../freemarker/test/templatesuite/testcases.xml    |  4 +-
 11 files changed, 196 insertions(+), 16 deletions(-)

diff --git a/src/main/java/freemarker/ext/beans/BeansModelCache.java b/src/main/java/freemarker/ext/beans/BeansModelCache.java
index 99374b6..ef214f0 100644
--- a/src/main/java/freemarker/ext/beans/BeansModelCache.java
+++ b/src/main/java/freemarker/ext/beans/BeansModelCache.java
@@ -71,4 +71,9 @@ public class BeansModelCache extends ModelCache {
         
         return factory.create(object, wrapper);
     }
+
+    void clearClassToFactoryMap() {
+        classToFactory.clear();
+    }
+
 }
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapper.java b/src/main/java/freemarker/ext/beans/BeansWrapper.java
index 7968aac..d654a63 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapper.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapper.java
@@ -45,7 +45,6 @@ import freemarker.core.BugException;
 import freemarker.core._DelayedFTLTypeDescription;
 import freemarker.core._DelayedShortClassName;
 import freemarker.core._TemplateModelException;
-import freemarker.ext.util.ModelCache;
 import freemarker.ext.util.ModelFactory;
 import freemarker.ext.util.WrapperTemplateModel;
 import freemarker.log.Logger;
@@ -170,7 +169,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      * Object to wrapped object cache; not used by default.
      * This object only belongs to a single {@link BeansWrapper}.
      */
-    private final ModelCache modelCache;
+    private final BeansModelCache modelCache;
 
     private final BooleanModel falseModel;
     private final BooleanModel trueModel;
@@ -189,7 +188,8 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
     private boolean simpleMapWrapper;  // initialized from the BeansWrapperConfiguration
     private boolean strict;  // initialized from the BeansWrapperConfiguration
     private boolean preferIndexedReadMethod; // initialized from the BeansWrapperConfiguration
-    
+    private boolean temporalSupport; // initialized from the BeansWrapperConfiguration
+
     private final Version incompatibleImprovements;
     
     /**
@@ -258,6 +258,12 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      *       The default of the {@link #setPreferIndexedReadMethod(boolean) preferIndexedReadMethod} setting changes
      *       from {@code true} to {@code false}.
      *     </li>  
+     *     <li>
+     *       <p>2.3.32 (or higher):
+     *       The default of {@link #setTemporalSupport(boolean) temporalSupport} changes to {@code true},
+     *       and thus {@link Temporal}-s (the Java 8 date/time classes) are wrapped into {@link TemplateTemporalModel}.
+     *       Before that, {@link Temporal}-s were treated as generic Java objects.
+     *       {@link TemplateTemporalModel}) was added in FreeMarker 2.3.32.
      *   </ul>
      *   
      *   <p>Note that the version will be normalized to the lowest version where the same incompatible
@@ -346,6 +352,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
         
         simpleMapWrapper = bwConf.isSimpleMapWrapper();
         preferIndexedReadMethod =  bwConf.getPreferIndexedReadMethod();
+        temporalSupport = bwConf.getTemporalSupport();
         defaultDateType = bwConf.getDefaultDateType();
         outerIdentity = bwConf.getOuterIdentity() != null ? bwConf.getOuterIdentity() : this;
         strict = bwConf.isStrict();
@@ -562,6 +569,33 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
     }
 
     /**
+     * Getter pair of {@link #setTemporalSupport(boolean)}
+     *
+     * @since 2.3.32
+     */
+    public boolean getTemporalSupport() {
+        return temporalSupport;
+    }
+
+    /**
+     * Sets if {@link Temporal}-s (the Java 8 date/time classes) are wrapped into a {@link TemplateTemporalModel}, or
+     * just are wrapped like generic java objects (and thus won't be formatted by FreeMarker properly, nor the built-ins
+     * made for them will work on them). The recommended value is {@code true}. But the defaults is {@code false} if the
+     * {@link BeansWrapper#BeansWrapper(Version) incompatibleImprovements} of the {@link BeansWrapper} is less than
+     * {@code 2.3.32}, otherwise it's {@code true}.
+     *
+     * @since 2.3.32
+     */
+    public void setTemporalSupport(boolean temporalSupport) {
+        checkModifiable();
+        if (temporalSupport != this.temporalSupport) {
+            this.temporalSupport = temporalSupport;
+            getModelCache().clearClassToFactoryMap();
+            getModelCache().clearCache();
+        }
+    }
+
+    /**
      * Sets the method exposure level. By default, set to <code>EXPOSE_SAFE</code>.
      * @param exposureLevel can be any of the <code>EXPOSE_xxx</code>
      * constants.
@@ -888,7 +922,8 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      */
     protected static Version normalizeIncompatibleImprovementsVersion(Version incompatibleImprovements) {
         _TemplateAPI.checkVersionNotNullAndSupported(incompatibleImprovements);
-        return incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_27 ? Configuration.VERSION_2_3_27
+        return incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_32 ? Configuration.VERSION_2_3_32
+                : incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_27 ? Configuration.VERSION_2_3_27
                 : incompatibleImprovements.intValue() == _TemplateAPI.VERSION_INT_2_3_26 ? Configuration.VERSION_2_3_26
                 : is2324Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_24
                 : is2321Bugfixed(incompatibleImprovements) ? Configuration.VERSION_2_3_21
@@ -921,7 +956,8 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
      * <li>if the object is null, returns the {@link #setNullModel(TemplateModel) null model},</li>
      * <li>if the object is a Number returns a {@link NumberModel} for it,</li>
      * <li>if the object is a Date returns a {@link DateModel} for it,</li>
-     * <li>if the object is a Boolean returns 
+     * <li>if the object is a java.time.Temporal returns a {@link TemporalModel} for it,</li>
+     * <li>if the object is a Boolean returns
      * {@link freemarker.template.TemplateBooleanModel#TRUE} or 
      * {@link freemarker.template.TemplateBooleanModel#FALSE}</li>
      * <li>if the object is already a TemplateModel, returns it unchanged,</li>
@@ -1012,7 +1048,10 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
         if (Date.class.isAssignableFrom(clazz)) {
             return DateModel.FACTORY;
         }
-        if (Boolean.class == clazz) { // Boolean is final 
+        if (temporalSupport && Temporal.class.isAssignableFrom(clazz)) {
+            return TemporalModel.FACTORY;
+        }
+        if (Boolean.class == clazz) { // Boolean is final
             return BOOLEAN_FACTORY;
         }
         if (ResourceBundle.class.isAssignableFrom(clazz)) {
@@ -1631,7 +1670,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
     }
     
     /** For Unit tests only */
-    ModelCache getModelCache() {
+    BeansModelCache getModelCache() {
         return modelCache;
     }
 
@@ -1850,6 +1889,7 @@ public class BeansWrapper implements RichObjectWrapper, WriteProtectable {
                + "exposureLevel=" + classIntrospector.getExposureLevel() + ", "
                + "exposeFields=" + classIntrospector.getExposeFields() + ", "
                + "preferIndexedReadMethod=" + preferIndexedReadMethod + ", "
+               + "temporalSupport=" + temporalSupport + ", "
                + "treatDefaultMethodsAsBeanMembers="
                + classIntrospector.getTreatDefaultMethodsAsBeanMembers() + ", "
                + "sharedClassIntrospCache="
diff --git a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
index 7be7ee4..f8b5151 100644
--- a/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
+++ b/src/main/java/freemarker/ext/beans/BeansWrapperConfiguration.java
@@ -47,6 +47,7 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
     // Properties and their *defaults*:
     private boolean simpleMapWrapper = false;
     private boolean preferIndexedReadMethod;
+    private boolean temporalSupport;
     private int defaultDateType = TemplateDateModel.UNKNOWN;
     private ObjectWrapper outerIdentity = null;
     private boolean strict = false;
@@ -90,6 +91,8 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
         this.incompatibleImprovements = incompatibleImprovements;
         
         preferIndexedReadMethod = incompatibleImprovements.intValue() < _TemplateAPI.VERSION_INT_2_3_27;
+
+        temporalSupport = incompatibleImprovements.intValue() >= _TemplateAPI.VERSION_INT_2_3_32;
         
         classIntrospectorBuilder = new ClassIntrospectorBuilder(incompatibleImprovements);
     }
@@ -108,6 +111,7 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
         result = prime * result + incompatibleImprovements.hashCode();
         result = prime * result + (simpleMapWrapper ? 1231 : 1237);
         result = prime * result + (preferIndexedReadMethod ? 1231 : 1237);
+        result = prime * result + (temporalSupport ? 1231 : 1237);
         result = prime * result + defaultDateType;
         result = prime * result + (outerIdentity != null ? outerIdentity.hashCode() : 0);
         result = prime * result + (strict ? 1231 : 1237);
@@ -130,6 +134,7 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
         if (!incompatibleImprovements.equals(other.incompatibleImprovements)) return false;
         if (simpleMapWrapper != other.simpleMapWrapper) return false;
         if (preferIndexedReadMethod != other.preferIndexedReadMethod) return false;
+        if (temporalSupport != other.temporalSupport) return false;
         if (defaultDateType != other.defaultDateType) return false;
         if (outerIdentity != other.outerIdentity) return false;
         if (strict != other.strict) return false;
@@ -171,6 +176,16 @@ public abstract class BeansWrapperConfiguration implements Cloneable {
         this.preferIndexedReadMethod = preferIndexedReadMethod;
     }
 
+    /** @since 2.3.32 */
+    public boolean getTemporalSupport() {
+        return temporalSupport;
+    }
+
+    /** See {@link BeansWrapper#setTemporalSupport(boolean)}. @since 2.3.32 */
+    public void setTemporalSupport(boolean temporalSupport) {
+        this.temporalSupport = temporalSupport;
+    }
+
     public int getDefaultDateType() {
         return defaultDateType;
     }
diff --git a/src/main/java/freemarker/ext/beans/TemporalModel.java b/src/main/java/freemarker/ext/beans/TemporalModel.java
new file mode 100644
index 0000000..16a69fe
--- /dev/null
+++ b/src/main/java/freemarker/ext/beans/TemporalModel.java
@@ -0,0 +1,61 @@
+/*
+ * 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.ext.beans;
+
+import java.time.temporal.Temporal;
+import java.util.Date;
+
+import freemarker.ext.util.ModelFactory;
+import freemarker.template.ObjectWrapper;
+import freemarker.template.TemplateDateModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateTemporalModel;
+
+/**
+ * Wraps arbitrary subclass of {@link Date} into a reflective model.
+ * Beside acting as a {@link TemplateDateModel}, you can call all Java methods
+ * on these objects as well.
+ */
+public class TemporalModel extends BeanModel implements TemplateTemporalModel {
+    static final ModelFactory FACTORY =
+        new ModelFactory()
+        {
+            @Override
+            public TemplateModel create(Object object, ObjectWrapper wrapper) {
+                return new TemporalModel((Temporal) object, (BeansWrapper) wrapper);
+            }
+        };
+
+    private final Temporal temporal;
+
+    public TemporalModel(Temporal temporal, BeansWrapper wrapper) {
+        super(temporal, wrapper);
+        if (temporal == null) {
+            throw new IllegalArgumentException("temporal == null");
+        }
+        this.temporal = temporal;
+    }
+
+    @Override
+    public Temporal getAsTemporal() {
+        return temporal;
+    }
+
+}
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index ed47cba..e6772d0 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -26,6 +26,7 @@ import java.lang.reflect.InvocationTargetException;
 import java.net.URLConnection;
 import java.text.DecimalFormat;
 import java.text.SimpleDateFormat;
+import java.time.temporal.Temporal;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.Date;
@@ -945,6 +946,14 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *       U+221E, and U+FFFD.
      *     </ul>
      *   </li>
+     *   <li><p>
+     *     2.3.32 (or higher):
+     *     <ul>
+     *       <li>2.3.32 (or higher):
+     *       {@link BeansWrapper} and {@link DefaultObjectWrapper} now wraps {@link Temporal}-s into
+     *       {@link SimpleTemporal}. Before that, {@link Temporal}-s were treated as generic Java objects;
+     *       see {@link BeansWrapper#BeansWrapper(Version)}.
+     *   </li>
      * </ul>
      * 
      * @throws IllegalArgumentException
diff --git a/src/main/java/freemarker/template/DefaultObjectWrapper.java b/src/main/java/freemarker/template/DefaultObjectWrapper.java
index 2636424..56bef33 100644
--- a/src/main/java/freemarker/template/DefaultObjectWrapper.java
+++ b/src/main/java/freemarker/template/DefaultObjectWrapper.java
@@ -81,7 +81,7 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
     /**
      * Creates a new instance with the incompatible-improvements-version specified in
      * {@link Configuration#DEFAULT_INCOMPATIBLE_IMPROVEMENTS}.
-     * 
+     *
      * @deprecated Use {@link DefaultObjectWrapperBuilder}, or in rare cases,
      *          {@link #DefaultObjectWrapper(Version)} instead.
      */
@@ -89,7 +89,7 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
     public DefaultObjectWrapper() {
         this(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
     }
-    
+
     /**
      * Use {@link DefaultObjectWrapperBuilder} instead if possible. Instances created with this constructor won't share
      * the class introspection caches with other instances. See {@link BeansWrapper#BeansWrapper(Version)} (the
@@ -112,7 +112,6 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
      *                  the default). This adapter is cleaner than {@link EnumerationModel} as it only implements the
      *                  minimally required FTL type, which avoids some ambiguous situations. (Note that Java API methods
      *                  aren't exposed anymore as subvariables; if you really need them, you can use {@code ?api}). 
-     *                  </li>
      *            </ul>
      * 
      * @since 2.3.21
@@ -211,7 +210,7 @@ public class DefaultObjectWrapper extends freemarker.ext.beans.BeansWrapper {
             }
             return new SimpleDate((java.util.Date) obj, getDefaultDateType());
         }
-        if (obj instanceof Temporal) {
+        if (getTemporalSupport() && obj instanceof Temporal) {
             return new SimpleTemporal((Temporal) obj);
         }
         final Class<?> objClass = obj.getClass();
diff --git a/src/main/java/freemarker/template/utility/ClassUtil.java b/src/main/java/freemarker/template/utility/ClassUtil.java
index 3fa4aa4..8faddc4 100644
--- a/src/main/java/freemarker/template/utility/ClassUtil.java
+++ b/src/main/java/freemarker/template/utility/ClassUtil.java
@@ -44,6 +44,7 @@ import freemarker.ext.beans.NumberModel;
 import freemarker.ext.beans.OverloadedMethodsModel;
 import freemarker.ext.beans.SimpleMethodModel;
 import freemarker.ext.beans.StringModel;
+import freemarker.ext.beans.TemporalModel;
 import freemarker.ext.util.WrapperTemplateModel;
 import freemarker.template.AdapterTemplateModel;
 import freemarker.template.TemplateBooleanModel;
@@ -214,6 +215,8 @@ public class ClassUtil {
                 return TemplateBooleanModel.class;
             } else if (tm instanceof DateModel) {
                 return TemplateDateModel.class;
+            } else if (tm instanceof TemporalModel) {
+                return TemporalModel.class;
             } else if (tm instanceof StringModel) {
                 Object wrapped = ((BeanModel) tm).getWrappedObject();
                 return wrapped instanceof String
diff --git a/src/test/java/freemarker/core/TemporalErrorMessagesTest.java b/src/test/java/freemarker/core/TemporalErrorMessagesTest.java
index e9c2791..1448d25 100644
--- a/src/test/java/freemarker/core/TemporalErrorMessagesTest.java
+++ b/src/test/java/freemarker/core/TemporalErrorMessagesTest.java
@@ -19,16 +19,21 @@
 
 package freemarker.core;
 
-import java.time.Instant;
 import java.time.LocalTime;
 
 import org.junit.Test;
 
+import freemarker.template.Configuration;
 import freemarker.template.TemplateException;
 import freemarker.test.TemplateTest;
 
 public class TemporalErrorMessagesTest extends TemplateTest {
 
+    @Override
+    protected Configuration createConfiguration() throws Exception {
+        return new Configuration(Configuration.VERSION_2_3_32);
+    }
+
     @Test
     public void testExplicitFormatString() throws TemplateException {
         addToDataModel("t", LocalTime.now());
diff --git a/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java b/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java
index ac7a7b7..0bc4273 100644
--- a/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java
+++ b/src/test/java/freemarker/ext/beans/BeansWrapperMiscTest.java
@@ -23,6 +23,7 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.lang.reflect.Modifier;
+import java.time.LocalDate;
 import java.util.Collections;
 
 import org.junit.Test;
@@ -37,6 +38,7 @@ import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateScalarModel;
 import freemarker.template.TemplateSequenceModel;
+import freemarker.template.TemplateTemporalModel;
 import freemarker.template.Version;
 import freemarker.template.utility.Constants;
 
@@ -117,7 +119,34 @@ public class BeansWrapperMiscTest {
             assertNull(barTM); // all read methods inaccessible
         }
     }
-    
+
+    @Test
+    public void testTemporalWrappingICI() throws TemplateModelException {
+        LocalDate localDate = LocalDate.of(2021, 10, 31);
+        {
+            BeansWrapper bw = new BeansWrapper(Configuration.VERSION_2_3_31);
+            assertFalse(bw.getTemporalSupport());
+            assertThat(
+                    bw.wrap(localDate),
+                    not(instanceOf(TemplateTemporalModel.class)));
+            bw.setTemporalSupport(true);
+            assertThat(
+                    bw.wrap(localDate),
+                    instanceOf(TemporalModel.class));
+        }
+        {
+            BeansWrapper bw = new BeansWrapper(Configuration.VERSION_2_3_32);
+            assertTrue(bw.getTemporalSupport());
+            assertThat(
+                    bw.wrap(localDate),
+                    instanceOf(TemporalModel.class));
+            bw.setTemporalSupport(false);
+            assertThat(
+                    bw.wrap(localDate),
+                    not(instanceOf(TemplateTemporalModel.class)));
+        }
+    }
+
     public static class BeanWithBothIndexedAndArrayProperty {
         
         private final static String[] FOO = new String[] { "a", "b" };
diff --git a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
index 17c6702..ee2a6b5 100644
--- a/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
+++ b/src/test/java/freemarker/template/DefaultObjectWrapperTest.java
@@ -26,6 +26,7 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.io.StringReader;
 import java.io.StringWriter;
+import java.time.LocalDate;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
@@ -103,7 +104,7 @@ public class DefaultObjectWrapperTest {
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.29
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.30
         expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.31
-        expected.add(Configuration.VERSION_2_3_27); // no non-BC change in 2.3.32
+        expected.add(Configuration.VERSION_2_3_32);
 
         List<Version> actual = new ArrayList<>();
         for (int i = _TemplateAPI.VERSION_INT_2_3_0; i <= Configuration.getVersion().intValue(); i++) {
@@ -1090,7 +1091,18 @@ public class DefaultObjectWrapperTest {
         assertFalse(new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27).build().getPreferIndexedReadMethod());
         assertFalse(new DefaultObjectWrapper(Configuration.VERSION_2_3_27).getPreferIndexedReadMethod());
     }
-    
+
+    @Test
+    public void testTemporalWrappingICI() throws TemplateModelException {
+        LocalDate localDate = LocalDate.of(2021, 10, 31);
+        assertThat(
+                new DefaultObjectWrapper(Configuration.VERSION_2_3_31).wrap(localDate),
+                not(instanceOf(TemplateTemporalModel.class)));
+        assertThat(
+                new DefaultObjectWrapper(Configuration.VERSION_2_3_32).wrap(localDate),
+                instanceOf(SimpleTemporal.class));
+    }
+
     private void assertSizeThroughAPIModel(int expectedSize, TemplateModel normalModel) throws TemplateModelException {
         if (!(normalModel instanceof TemplateModelWithAPISupport)) {
             fail(); 
diff --git a/src/test/resources/freemarker/test/templatesuite/testcases.xml b/src/test/resources/freemarker/test/templatesuite/testcases.xml
index c918926..d0f66bb 100644
--- a/src/test/resources/freemarker/test/templatesuite/testcases.xml
+++ b/src/test/resources/freemarker/test/templatesuite/testcases.xml
@@ -220,7 +220,9 @@
       <setting incompatible_improvements="2.3.24, max"/>
    </testCase>
    <testCase name="date-type-builtins" noOutput="true" />
-   <testCase name="temporal" noOutput="true" />
+   <testCase name="temporal" noOutput="true">
+      <setting incompatible_improvements="2.3.32, max"/>
+   </testCase>
    <testCase name="url" noOutput="true" />
    <testCase name="var-layers"/>
    <testCase name="variables"/>