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"/>