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/10/27 22:12:59 UTC
[freemarker] branch FREEMARKER-35 updated: [FREEMARKER-35]
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
The following commit(s) were added to refs/heads/FREEMARKER-35 by this push:
new 9268063 [FREEMARKER-35]
9268063 is described below
commit 9268063760c95c77d0f131e41b4ba00d6b5d5d4e
Author: ddekany <dd...@apache.org>
AuthorDate: Thu Oct 28 00:10:32 2021 +0200
[FREEMARKER-35]
- Added missing Configurable related functionality: setters, is...Set methods, TemplateConfiguration apply and merge method updates
- Some code cleanup, including javaDocs
---
src/main/java/freemarker/core/Configurable.java | 183 +++++++++++++++++++--
.../ISOLikeTemplateTemporalTemporalFormat.java | 6 +
.../core/ISOTemplateTemporalFormatFactory.java | 17 +-
.../core/JavaTemplateTemporalFormat.java | 13 +-
.../freemarker/core/TemplateConfiguration.java | 63 +++++++
.../freemarker/core/TemplateDateFormatFactory.java | 2 +-
.../core/TemplateNumberFormatFactory.java | 2 +-
.../freemarker/core/TemplateTemporalFormat.java | 13 +-
.../core/TemplateTemporalFormatFactory.java | 15 +-
.../core/ToStringTemplateTemporalFormat.java | 8 +-
.../ToStringTemplateTemporalFormatFactory.java | 7 +
.../core/UnformattableTemporalTypeException.java | 2 +-
.../core/XSTemplateTemporalFormatFactory.java | 57 +------
.../freemarker/core/TemplateConfigurationTest.java | 37 ++++-
14 files changed, 330 insertions(+), 95 deletions(-)
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index adb513d..c1ffce9 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -173,6 +173,10 @@ public class Configurable {
public static final String OFFSET_TIME_FORMAT_KEY_CAMEL_CASE = "offsetTimeFormat";
public static final String OFFSET_TIME_FORMAT_KEY = OFFSET_TIME_FORMAT_KEY_SNAKE_CASE;
+ public static final String ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE = "zoned_date_time_format";
+ public static final String ZONED_DATE_TIME_FORMAT_KEY_CAMEL_CASE = "zonedDateTimeFormat";
+ public static final String ZONED_DATE_TIME_FORMAT_KEY = ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE;
+
public static final String YEAR_FORMAT_KEY_SNAKE_CASE = "year_format";
public static final String YEAR_FORMAT_KEY_CAMEL_CASE = "yearFormat";
public static final String YEAR_FORMAT_KEY = YEAR_FORMAT_KEY_SNAKE_CASE;
@@ -181,10 +185,6 @@ public class Configurable {
public static final String YEAR_MONTH_FORMAT_KEY_CAMEL_CASE = "yearMonthFormat";
public static final String YEAR_MONTH_FORMAT_KEY = YEAR_MONTH_FORMAT_KEY_SNAKE_CASE;
- public static final String ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE = "zoned_date_time_format";
- public static final String ZONED_DATE_TIME_FORMAT_KEY_CAMEL_CASE = "zonedDateTimeFormat";
- public static final String ZONED_DATE_TIME_FORMAT_KEY = ZONED_DATE_TIME_FORMAT_KEY_SNAKE_CASE;
-
/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
public static final String TIME_ZONE_KEY_SNAKE_CASE = "time_zone";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
@@ -442,9 +442,9 @@ public class Configurable {
private String localTimeFormat;
private String offsetDateTimeFormat;
private String offsetTimeFormat;
+ private String zonedDateTimeFormat;
private String yearFormat;
private String yearMonthFormat;
- private String zonedDateTimeFormat;
private TimeZone timeZone;
private TimeZone sqlDataAndTimeTimeZone;
private boolean sqlDataAndTimeTimeZoneSet;
@@ -535,15 +535,15 @@ public class Configurable {
offsetTimeFormat = "";
properties.setProperty(OFFSET_TIME_FORMAT_KEY, offsetTimeFormat);
+ zonedDateTimeFormat = "";
+ properties.setProperty(ZONED_DATE_TIME_FORMAT_KEY, zonedDateTimeFormat);
+
yearFormat = "";
properties.setProperty(YEAR_FORMAT_KEY, yearFormat);
yearMonthFormat = "";
properties.setProperty(YEAR_MONTH_FORMAT_KEY, yearMonthFormat);
- zonedDateTimeFormat = "";
- properties.setProperty(ZONED_DATE_TIME_FORMAT_KEY, zonedDateTimeFormat);
-
classicCompatible = Integer.valueOf(0);
properties.setProperty(CLASSIC_COMPATIBLE_KEY, classicCompatible.toString());
@@ -1369,40 +1369,199 @@ public class Configurable {
return dateTimeFormat != null;
}
+ /**
+ * Sets the format used to convert {@link java.time.Instant}-s to string-s, also the format that
+ * {@code someString?instant} will use to parse strings.
+ * <p>Defaults to TODO [FREEMARKER-35].
+ * @since 2.3.32
+ */
+ public void setInstantFormat(String instantFormat) {
+ this.instantFormat = instantFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setInstantFormat(String)}.
+ * @since 2.3.32
+ */
public String getInstantFormat() {
- return instantFormat == null ? parent.getInstantFormat() : instantFormat;
+ return instantFormat == null ? parent.getInstantFormat() : instantFormat;
}
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isInstantFormatSet() {
+ return instantFormat != null;
+ }
+
+ public void setLocalDateFormat(String localDateFormat) {
+ this.localDateFormat = localDateFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setLocalDateFormat(String)}.
+ * @since 2.3.32
+ */
public String getLocalDateFormat() {
return localDateFormat == null ? parent.getLocalDateFormat() : localDateFormat;
}
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isLocalDateFormatSet() {
+ return localDateFormat != null;
+ }
+
+ public void setLocalDateTimeFormat(String localDateTimeFormat) {
+ this.localDateTimeFormat = localDateTimeFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setLocalDateTimeFormat(String)}.
+ * @since 2.3.32
+ */
public String getLocalDateTimeFormat() {
return localDateTimeFormat == null ? parent.getLocalDateTimeFormat() : localDateTimeFormat;
}
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isLocalDateTimeFormatSet() {
+ return localDateTimeFormat != null;
+ }
+
+ public void setLocalTimeFormat(String localTimeFormat) {
+ this.localTimeFormat = localTimeFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setLocalTimeFormat(String)}.
+ * @since 2.3.32
+ */
public String getLocalTimeFormat() {
return localTimeFormat == null ? parent.getLocalTimeFormat() : localTimeFormat;
}
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isLocalTimeFormatSet() {
+ return localTimeFormat != null;
+ }
+
+ public void setOffsetDateTimeFormat(String offsetDateTimeFormat) {
+ this.offsetDateTimeFormat = offsetDateTimeFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setOffsetDateTimeFormat(String)}.
+ * @since 2.3.32
+ */
public String getOffsetDateTimeFormat() {
return offsetDateTimeFormat == null ? parent.getOffsetDateTimeFormat() : offsetDateTimeFormat;
}
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isOffsetDateTimeFormatSet() {
+ return offsetDateTimeFormat != null;
+ }
+
+ public void setOffsetTimeFormat(String offsetTimeFormat) {
+ this.offsetTimeFormat = offsetTimeFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setOffsetTimeFormat(String)}.
+ * @since 2.3.32
+ */
public String getOffsetTimeFormat() {
return offsetTimeFormat == null ? parent.getOffsetTimeFormat() : offsetTimeFormat;
}
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isOffsetTimeFormatSet() {
+ return offsetTimeFormat != null;
+ }
+
+ public void setZonedDateTimeFormat(String zonedDateTimeFormat) {
+ this.zonedDateTimeFormat = zonedDateTimeFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setZonedDateTimeFormat(String)}.
+ * @since 2.3.32
+ */
+ public String getZonedDateTimeFormat() {
+ return zonedDateTimeFormat == null ? parent.getZonedDateTimeFormat() : zonedDateTimeFormat;
+ }
+
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isZonedDateTimeFormatSet() {
+ return zonedDateTimeFormat != null;
+ }
+
+ public void setYearFormat(String yearFormat) {
+ this.yearFormat = yearFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setYearFormat(String)}.
+ * @since 2.3.32
+ */
public String getYearFormat() {
return yearFormat == null ? parent.getYearFormat() : yearFormat;
}
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isYearFormatSet() {
+ return yearFormat != null;
+ }
+
+ public void setYearMonthFormat(String yearMonthFormat) {
+ this.yearMonthFormat = yearMonthFormat;
+ }
+
+ /**
+ * Getter pair of {@link #setYearMonthFormat(String)}.
+ * @since 2.3.32
+ */
public String getYearMonthFormat() {
return yearMonthFormat == null ? parent.getYearMonthFormat() : yearMonthFormat;
}
- public String getZonedDateTimeFormat() {
- return zonedDateTimeFormat == null ? parent.getZonedDateTimeFormat() : zonedDateTimeFormat;
+ /**
+ * Tells if this setting is set directly in this object or its value is coming from the {@link #getParent() parent}.
+ *
+ * @since 2.3.32
+ */
+ public boolean isYearMonthFormatSet() {
+ return yearMonthFormat != null;
}
/**
@@ -1417,7 +1576,7 @@ public class Configurable {
* @throws NullPointerException If {@link temporalClass} was {@code null}
* @throws IllegalArgumentException If {@link temporalClass} is not a supported {@link Temporal} subclass.
*
- * @since 2.3.31
+ * @since 2.3.32
*/
public String getTemporalFormat(Class<? extends Temporal> temporalClass) {
Objects.requireNonNull(temporalClass);
diff --git a/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java b/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
index 87eb859..fc500e0 100644
--- a/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
+++ b/src/main/java/freemarker/core/ISOLikeTemplateTemporalTemporalFormat.java
@@ -30,6 +30,12 @@ import freemarker.template.TemplateModelException;
import freemarker.template.TemplateTemporalModel;
// TODO [FREEMARKER-35] These should support parameters similar to {@link ISOTemplateDateFormat},
+
+/**
+ * See {@link ISOTemplateTemporalFormatFactory}, and {@link XSTemplateTemporalFormatFactory}.
+ *
+ * @since 2.3.32
+ */
final class ISOLikeTemplateTemporalTemporalFormat extends TemplateTemporalFormat {
private final DateTimeFormatter dateTimeFormatter;
private final boolean instantConversion;
diff --git a/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java
index e932d3f..50edb2c 100644
--- a/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/ISOTemplateTemporalFormatFactory.java
@@ -31,6 +31,9 @@ import java.time.temporal.Temporal;
import java.util.Locale;
import java.util.TimeZone;
+/**
+ * Format factory related to {@link someJava8Temporal?string.iso}, {@link someJava8Temporal?string.iso_...}, etc.
+ */
class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
static final ISOTemplateTemporalFormatFactory INSTANCE = new ISOTemplateTemporalFormatFactory();
@@ -39,14 +42,13 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
// Not meant to be called from outside
}
- private static final DateTimeFormatter ISO8601_DATE_FORMAT = new DateTimeFormatterBuilder()
+ static final DateTimeFormatter ISO8601_DATE_FORMAT = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
.toFormatter()
.withLocale(Locale.US);
- private static final DateTimeFormatter ISO8601_DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
+ static final DateTimeFormatter ISO8601_DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
.append(DateTimeFormatter.ISO_LOCAL_DATE)
- .optionalStart()
.appendLiteral('T')
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(":")
@@ -57,11 +59,10 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
.optionalStart()
.appendOffsetId()
.optionalEnd()
- .optionalEnd()
.toFormatter()
.withLocale(Locale.US);
- private static final DateTimeFormatter ISO8601_TIME_FORMAT = new DateTimeFormatterBuilder()
+ static final DateTimeFormatter ISO8601_TIME_FORMAT = new DateTimeFormatterBuilder()
.appendValue(ChronoField.HOUR_OF_DAY, 2)
.appendLiteral(":")
.appendValue(ChronoField.MINUTE_OF_HOUR, 2)
@@ -74,7 +75,7 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
.toFormatter()
.withLocale(Locale.US);
- private static final DateTimeFormatter ISO8601_YEARMONTH_FORMAT = new DateTimeFormatterBuilder()
+ static final DateTimeFormatter ISO8601_YEARMONTH_FORMAT = new DateTimeFormatterBuilder()
.appendValue(ChronoField.YEAR)
.appendLiteral("-")
.appendValue(ChronoField.MONTH_OF_YEAR, 2)
@@ -91,7 +92,7 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
TemplateValueFormatException {
if (!params.isEmpty()) {
// TODO [FREEMARKER-35]
- throw new InvalidFormatParametersException("xs currently doesn't support parameters");
+ throw new InvalidFormatParametersException("iso currently doesn't support parameters for Java 8 temporal types");
}
return getISOFormatter(temporalClass, timeZone);
@@ -104,7 +105,7 @@ class ISOTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
dateTimeFormatter = ISO8601_TIME_FORMAT;
description = "ISO 8601 (subset) time";
} else if (temporalClass == Year.class) {
- dateTimeFormatter = ISO8601_YEAR_FORMAT; // Same as ISO
+ dateTimeFormatter = ISO8601_YEAR_FORMAT;
description = "ISO 8601 (subset) year";
} else if (temporalClass == YearMonth.class) {
dateTimeFormatter = ISO8601_YEARMONTH_FORMAT;
diff --git a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
index da966fb..5482826 100644
--- a/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateTemporalFormat.java
@@ -40,6 +40,11 @@ import freemarker.template.TemplateTemporalModel;
import freemarker.template.utility.ClassUtil;
import freemarker.template.utility.StringUtil;
+/**
+ * See {@link JavaTemplateTemporalFormatFactory}.
+ *
+ * @since 2.3.32
+ */
class JavaTemplateTemporalFormat extends TemplateTemporalFormat {
enum FormatTimeConversion {
@@ -47,7 +52,13 @@ class JavaTemplateTemporalFormat extends TemplateTemporalFormat {
SET_ZONE_FROM_OFFSET
}
- private static final Pattern FORMAT_STYLE_PATTERN = Pattern.compile("(short|medium|long|full)(?:_(short|medium|long|full))?");
+ static final String SHORT = "short";
+ static final String MEDIUM = "medium";
+ static final String LONG = "long";
+ static final String FULL = "full";
+ private static final String ANY_FORMAT_STYLE = "(" + SHORT + "|" + MEDIUM + "|" + LONG + "|" + FULL + ")";
+ private static final Pattern FORMAT_STYLE_PATTERN = Pattern.compile(
+ ANY_FORMAT_STYLE + "(?:_" + ANY_FORMAT_STYLE + ")?");
private final DateTimeFormatter dateTimeFormatter;
private final ZoneId zoneId;
diff --git a/src/main/java/freemarker/core/TemplateConfiguration.java b/src/main/java/freemarker/core/TemplateConfiguration.java
index 92cc671..cd8410f 100644
--- a/src/main/java/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/freemarker/core/TemplateConfiguration.java
@@ -190,6 +190,33 @@ public final class TemplateConfiguration extends Configurable implements ParserC
if (tc.isDateTimeFormatSet()) {
setDateTimeFormat(tc.getDateTimeFormat());
}
+ if (tc.isInstantFormatSet()) {
+ setInstantFormat(tc.getInstantFormat());
+ }
+ if (tc.isLocalDateFormatSet()) {
+ setLocalDateFormat(tc.getLocalDateFormat());
+ }
+ if (tc.isLocalDateTimeFormatSet()) {
+ setLocalDateTimeFormat(tc.getLocalDateTimeFormat());
+ }
+ if (tc.isLocalTimeFormatSet()) {
+ setLocalTimeFormat(tc.getLocalTimeFormat());
+ }
+ if (tc.isOffsetDateTimeFormatSet()) {
+ setOffsetDateTimeFormat(tc.getOffsetDateTimeFormat());
+ }
+ if (tc.isOffsetTimeFormatSet()) {
+ setOffsetTimeFormat(tc.getOffsetTimeFormat());
+ }
+ if (tc.isZonedDateTimeFormatSet()) {
+ setZonedDateTimeFormat(tc.getZonedDateTimeFormat());
+ }
+ if (tc.isYearFormatSet()) {
+ setYearFormat(tc.getYearFormat());
+ }
+ if (tc.isYearMonthFormatSet()) {
+ setYearMonthFormat(tc.getYearMonthFormat());
+ }
if (tc.isEncodingSet()) {
setEncoding(tc.getEncoding());
}
@@ -332,6 +359,33 @@ public final class TemplateConfiguration extends Configurable implements ParserC
if (isDateTimeFormatSet() && !template.isDateTimeFormatSet()) {
template.setDateTimeFormat(getDateTimeFormat());
}
+ if (isInstantFormatSet() && !template.isInstantFormatSet()) {
+ template.setInstantFormat(getInstantFormat());
+ }
+ if (isLocalDateFormatSet() && !template.isLocalDateFormatSet()) {
+ template.setLocalDateFormat(getLocalDateFormat());
+ }
+ if (isLocalTimeFormatSet() && !template.isLocalTimeFormatSet()) {
+ template.setLocalTimeFormat(getLocalTimeFormat());
+ }
+ if (isLocalDateTimeFormatSet() && !template.isLocalDateTimeFormatSet()) {
+ template.setLocalDateTimeFormat(getLocalDateTimeFormat());
+ }
+ if (isOffsetTimeFormatSet() && !template.isOffsetTimeFormatSet()) {
+ template.setOffsetTimeFormat(getOffsetTimeFormat());
+ }
+ if (isOffsetDateTimeFormatSet() && !template.isOffsetDateTimeFormatSet()) {
+ template.setOffsetDateTimeFormat(getOffsetDateTimeFormat());
+ }
+ if (isZonedDateTimeFormatSet() && !template.isZonedDateTimeFormatSet()) {
+ template.setZonedDateTimeFormat(getZonedDateTimeFormat());
+ }
+ if (isYearFormatSet() && !template.isYearFormatSet()) {
+ template.setYearFormat(getYearFormat());
+ }
+ if (isYearMonthFormatSet() && !template.isYearMonthFormatSet()) {
+ template.setYearMonthFormat(getYearMonthFormat());
+ }
if (isEncodingSet() && template.getEncoding() == null) {
template.setEncoding(getEncoding());
}
@@ -679,6 +733,15 @@ public final class TemplateConfiguration extends Configurable implements ParserC
|| isCustomNumberFormatsSet()
|| isDateFormatSet()
|| isDateTimeFormatSet()
+ || isInstantFormatSet()
+ || isLocalDateFormatSet()
+ || isLocalTimeFormatSet()
+ || isLocalDateTimeFormatSet()
+ || isOffsetTimeFormatSet()
+ || isOffsetDateTimeFormatSet()
+ || isZonedDateTimeFormatSet()
+ || isYearFormatSet()
+ || isYearMonthFormatSet()
|| isLazyImportsSet()
|| isLazyAutoImportsSet()
|| isLocaleSet()
diff --git a/src/main/java/freemarker/core/TemplateDateFormatFactory.java b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
index 38cae8b..81faa19 100644
--- a/src/main/java/freemarker/core/TemplateDateFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
@@ -76,7 +76,7 @@ public abstract class TemplateDateFormatFactory extends TemplateValueFormatFacto
* @param env
* The runtime environment from which the formatting was called. This is mostly meant to be used for
* {@link Environment#setCustomState(Object, Object)}/{@link Environment#getCustomState(Object)}. The
- * result shouldn't depend on setting values in the {@link Environment}, as changing other setting
+ * result shouldn't depend on setting values in the {@link Environment}, because changing settings
* will not necessarily invalidate the result.
*
* @throws TemplateValueFormatException
diff --git a/src/main/java/freemarker/core/TemplateNumberFormatFactory.java b/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
index 684fef2..4180bea 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
@@ -53,7 +53,7 @@ public abstract class TemplateNumberFormatFactory extends TemplateValueFormatFac
* @param env
* The runtime environment from which the formatting was called. This is mostly meant to be used for
* {@link Environment#setCustomState(Object, Object)}/{@link Environment#getCustomState(Object)}. The
- * result shouldn't depend on setting values in the {@link Environment}, as changing other setting
+ * result shouldn't depend on setting values in the {@link Environment}, because changing settings
* will not necessarily invalidate the result.
*
* @throws TemplateValueFormatException
diff --git a/src/main/java/freemarker/core/TemplateTemporalFormat.java b/src/main/java/freemarker/core/TemplateTemporalFormat.java
index f41fb0c..39116a6 100644
--- a/src/main/java/freemarker/core/TemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/TemplateTemporalFormat.java
@@ -30,22 +30,25 @@ import freemarker.template.TemplateTemporalModel;
*
* <p>
* Implementations need not be thread-safe if the {@link TemplateTemporalFormatFactory} doesn't recycle them among
- * different {@link Environment}-s. As far as FreeMarker's concerned, instances are bound to a single
- * {@link Environment}, and {@link Environment}-s are thread-local objects.
+ * different {@link Environment}-s. The code outside the {@link TemplateTemporalFormatFactory} will not try to reuse
+ * {@link TemplateTemporalFormat} instances in multiple {@link Environment}-s, and {@link Environment}-s are
+ * thread-local objects.
*
- * @since 2.3.31
+ * @since 2.3.32
*/
public abstract class TemplateTemporalFormat extends TemplateValueFormat {
public abstract String format(TemplateTemporalModel temporalModel) throws TemplateValueFormatException, TemplateModelException;
/**
- * Tells if this formatter should be re-created if the locale changes.
+ * Tells if the same formatter can be used regardless of the desired locale (so for example after a
+ * {@link Environment#getLocale()} change we can keep using the old instance).
*/
public abstract boolean isLocaleBound();
/**
- * Tells if this formatter should be re-created if the time zone changes.
+ * Tells if the same formatter can be used regardless of the desired time zone (so for example after a
+ * {@link Environment#getTimeZone()} change we can keep using the old instance).
*/
public abstract boolean isTimeZoneBound();
diff --git a/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
index ae3971b..885ea7b 100644
--- a/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateTemporalFormatFactory.java
@@ -18,22 +18,19 @@
*/
package freemarker.core;
-import java.text.SimpleDateFormat;
import java.time.temporal.Temporal;
-import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import freemarker.template.Configuration;
-import freemarker.template.TemplateDateModel;
/**
* Factory for a certain kind of {@link Temporal} formatting ({@link TemplateTemporalFormat}). Usually a singleton
- * (one-per-VM or one-per-{@link Configuration}), and so must be thread-safe.
+ * (one-per-VM, or one-per-{@link Configuration}), and so must be thread-safe.
*
* TODO [FREEMARKER-35] @see Configurable#setCustomTemporalFormats(java.util.Map)
*
- * @since 2.3.24
+ * @since 2.3.32
*/
public abstract class TemplateTemporalFormatFactory extends TemplateValueFormatFactory {
@@ -49,7 +46,7 @@ public abstract class TemplateTemporalFormatFactory extends TemplateValueFormatF
*
* @param params
* The string that further describes how the format should look. For example, when the
- * {@link Configurable#getInstantFormat()} ()} instantFormat} is {@code "@fooBar 1, 2"}, then it will be
+ * {@link Configurable#getInstantFormat() instantFormat} is {@code "@fooBar 1, 2"}, then it will be
* {@code "1, 2"} (and {@code "@fooBar"} selects the factory). The format of this string is up to the
* {@link TemplateTemporalFormatFactory} implementation. Not {@code null}, often an empty string.
* @param temporalClass
@@ -57,14 +54,14 @@ public abstract class TemplateTemporalFormatFactory extends TemplateValueFormatF
* {@link UnformattableTemporalTypeException} exception.
* @param locale
* The locale to format for. Not {@code null}. The resulting format should be bound to this locale
- * forever (i.e. locale changes in the {@link Environment} must not be followed).
+ * forever. That is, the result of {@link Environment#getLocale()} must not be taken into account.
* @param timeZone
* The time zone to format for. Not {@code null}. The resulting format must be bound to this time zone
- * forever (i.e. time zone changes in the {@link Environment} must not be followed).
+ * forever. That is, the result of {@link Environment#getTimeZone()} must not be taken into account.
* @param env
* The runtime environment from which the formatting was called. This is mostly meant to be used for
* {@link Environment#setCustomState(Object, Object)}/{@link Environment#getCustomState(Object)}. The
- * result shouldn't depend on setting values in the {@link Environment}, as changing other setting
+ * result shouldn't depend on setting values in the {@link Environment}, because changing settings
* will not necessarily invalidate the result.
*
* @throws TemplateValueFormatException
diff --git a/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java b/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java
index 3bd6d64..4e3dd5b 100644
--- a/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java
+++ b/src/main/java/freemarker/core/ToStringTemplateTemporalFormat.java
@@ -22,12 +22,18 @@ package freemarker.core;
import java.time.Instant;
import java.time.ZoneId;
import java.time.temporal.Temporal;
-import java.util.Objects;
import java.util.TimeZone;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateTemporalModel;
+/**
+ * See {@link ToStringTemplateTemporalFormatFactory}.
+ *
+ * @Deprected TODO [FREEMARKER-35] I guess we shouldn't need this.
+ *
+ * @since 2.3.32
+ */
class ToStringTemplateTemporalFormat extends TemplateTemporalFormat {
private final ZoneId timeZone;
diff --git a/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java
index 951471e..a2e53df 100644
--- a/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/ToStringTemplateTemporalFormatFactory.java
@@ -23,6 +23,13 @@ import java.time.temporal.Temporal;
import java.util.Locale;
import java.util.TimeZone;
+/**
+ * Gives a {@link TemplateTemporalFormat} that simply calls {@link Object#toString()}
+ *
+ * @Deprected TODO [FREEMARKER-35] I guess we shouldn't need this.
+ *
+ * @since 2.3.32
+ */
class ToStringTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
static final ToStringTemplateTemporalFormatFactory INSTANCE = new ToStringTemplateTemporalFormatFactory();
diff --git a/src/main/java/freemarker/core/UnformattableTemporalTypeException.java b/src/main/java/freemarker/core/UnformattableTemporalTypeException.java
index a4f377a..34a75db 100644
--- a/src/main/java/freemarker/core/UnformattableTemporalTypeException.java
+++ b/src/main/java/freemarker/core/UnformattableTemporalTypeException.java
@@ -27,7 +27,7 @@ import freemarker.template.TemplateTemporalModel;
* Thrown when a {@link TemplateTemporalModel} can't be formatted because the {@link TemplateTemporalFormatFactory}
* doesn't support it.
*
- * @since 2.3.31
+ * @since 2.3.32
*/
public final class UnformattableTemporalTypeException extends UnformattableValueException {
diff --git a/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java b/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java
index 8749c74..f41c0c5 100644
--- a/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java
+++ b/src/main/java/freemarker/core/XSTemplateTemporalFormatFactory.java
@@ -27,12 +27,13 @@ import java.time.OffsetTime;
import java.time.Year;
import java.time.YearMonth;
import java.time.format.DateTimeFormatter;
-import java.time.format.DateTimeFormatterBuilder;
-import java.time.temporal.ChronoField;
import java.time.temporal.Temporal;
import java.util.Locale;
import java.util.TimeZone;
+/**
+ * Format factory related to {@link someJava8Temporal?string.xs}, {@link someJava8Temporal?string.xs_...}, etc.
+ */
class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
static final XSTemplateTemporalFormatFactory INSTANCE = new XSTemplateTemporalFormatFactory();
@@ -41,52 +42,12 @@ class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
// Not meant to be called from outside
}
- private static final DateTimeFormatter XSD_DATE_FORMAT = new DateTimeFormatterBuilder()
- .append(DateTimeFormatter.ISO_LOCAL_DATE)
- .toFormatter()
- .withLocale(Locale.US);
-
- private static final DateTimeFormatter XSD_DATE_TIME_FORMAT = new DateTimeFormatterBuilder()
- .append(DateTimeFormatter.ISO_LOCAL_DATE)
- .appendLiteral('T')
- .appendValue(ChronoField.HOUR_OF_DAY, 2)
- .appendLiteral(":")
- .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
- .appendLiteral(":")
- .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
- .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
- .optionalStart()
- .appendOffsetId()
- .optionalEnd()
- .toFormatter()
- .withLocale(Locale.US);
-
- private static final DateTimeFormatter XSD_TIME_FORMAT = new DateTimeFormatterBuilder()
- .appendValue(ChronoField.HOUR_OF_DAY, 2)
- .appendLiteral(":")
- .appendValue(ChronoField.MINUTE_OF_HOUR, 2)
- .appendLiteral(":")
- .appendValue(ChronoField.SECOND_OF_MINUTE, 2)
- .appendFraction(ChronoField.MILLI_OF_SECOND, 0, 3, true)
- .optionalStart()
- .appendOffsetId()
- .optionalEnd()
- .toFormatter()
- .withLocale(Locale.US);
-
- private static final DateTimeFormatter XSD_YEARMONTH_FORMAT = new DateTimeFormatterBuilder()
- .appendValue(ChronoField.YEAR)
- .appendLiteral("-")
- .appendValue(ChronoField.MONTH_OF_YEAR, 2)
- .toFormatter()
- .withLocale(Locale.US);
-
@Override
public TemplateTemporalFormat get(String params, Class<? extends Temporal> temporalClass, Locale locale, TimeZone timeZone, Environment env) throws
TemplateValueFormatException {
if (!params.isEmpty()) {
// TODO [FREEMARKER-35]
- throw new InvalidFormatParametersException("xs currently doesn't support parameters");
+ throw new InvalidFormatParametersException("xs currently doesn't support parameters for Java 8 temporal types");
}
return getXSFormatter(temporalClass, timeZone);
@@ -96,16 +57,16 @@ class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
final DateTimeFormatter dateTimeFormatter;
final String description;
if (temporalClass == LocalTime.class || temporalClass == OffsetTime.class) {
- dateTimeFormatter = XSD_TIME_FORMAT;
+ dateTimeFormatter = ISO8601_TIME_FORMAT;
description = "W3C XML Schema time";
} else if (temporalClass == Year.class) {
- dateTimeFormatter = ISO8601_YEAR_FORMAT; // Same as ISO
+ dateTimeFormatter = ISO8601_YEAR_FORMAT;
description = "W3C XML Schema year";
} else if (temporalClass == YearMonth.class) {
- dateTimeFormatter = XSD_YEARMONTH_FORMAT;
+ dateTimeFormatter = ISO8601_YEARMONTH_FORMAT;
description = "W3C XML Schema year-month";
} else if (temporalClass == LocalDate.class) {
- dateTimeFormatter = XSD_DATE_FORMAT;
+ dateTimeFormatter = ISO8601_DATE_FORMAT;
description = "W3C XML Schema date";
} else {
Class<? extends Temporal> normTemporalClass =
@@ -113,7 +74,7 @@ class XSTemplateTemporalFormatFactory extends TemplateTemporalFormatFactory {
if (normTemporalClass != temporalClass) {
return getXSFormatter(normTemporalClass, timeZone);
} else {
- dateTimeFormatter = XSD_DATE_TIME_FORMAT;
+ dateTimeFormatter = ISO8601_DATE_TIME_FORMAT;
description = "W3C XML Schema date-time";
}
}
diff --git a/src/test/java/freemarker/core/TemplateConfigurationTest.java b/src/test/java/freemarker/core/TemplateConfigurationTest.java
index e48c38a..57ed326 100644
--- a/src/test/java/freemarker/core/TemplateConfigurationTest.java
+++ b/src/test/java/freemarker/core/TemplateConfigurationTest.java
@@ -163,6 +163,16 @@ public class TemplateConfigurationTest {
SETTING_ASSIGNMENTS.put("classicCompatibleAsInt", 2);
SETTING_ASSIGNMENTS.put("dateFormat", "yyyy-#DDD");
SETTING_ASSIGNMENTS.put("dateTimeFormat", "yyyy-#DDD-@HH:mm");
+ SETTING_ASSIGNMENTS.put("instantFormat", "iso");
+ SETTING_ASSIGNMENTS.put("localDateFormat", "iso");
+ SETTING_ASSIGNMENTS.put("localTimeFormat", "iso");
+ SETTING_ASSIGNMENTS.put("localDateTimeFormat", "iso");
+ SETTING_ASSIGNMENTS.put("offsetTimeFormat", "iso");
+ SETTING_ASSIGNMENTS.put("offsetDateTimeFormat", "iso");
+ SETTING_ASSIGNMENTS.put("zonedTimeFormat", "iso");
+ SETTING_ASSIGNMENTS.put("zonedDateTimeFormat", "iso");
+ SETTING_ASSIGNMENTS.put("yearFormat", "yyyy");
+ SETTING_ASSIGNMENTS.put("yearMonthFormat", "yyyy-MM");
SETTING_ASSIGNMENTS.put("locale", NON_DEFAULT_LOCALE);
SETTING_ASSIGNMENTS.put("logTemplateExceptions", false);
SETTING_ASSIGNMENTS.put("wrapUncheckedExceptions", true);
@@ -245,7 +255,6 @@ public class TemplateConfigurationTest {
static {
IGNORED_PROP_NAMES = new HashSet();
- IGNORED_PROP_NAMES.add("class");
IGNORED_PROP_NAMES.add("strictBeanModels");
IGNORED_PROP_NAMES.add("parentConfiguration");
IGNORED_PROP_NAMES.add("settings");
@@ -258,7 +267,8 @@ public class TemplateConfigurationTest {
try {
for (PropertyDescriptor propDesc : Introspector.getBeanInfo(Configurable.class).getPropertyDescriptors()) {
String propName = propDesc.getName();
- if (!IGNORED_PROP_NAMES.contains(propName)) {
+ if (!IGNORED_PROP_NAMES.contains(propName) && propDesc.getWriteMethod() != null
+ && propDesc.getWriteMethod().getDeclaringClass() != Object.class) {
CONFIGURABLE_PROP_NAMES.add(propName);
}
}
@@ -541,16 +551,20 @@ public class TemplateConfigurationTest {
for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(false, true)) {
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
-
- Object newValue = SETTING_ASSIGNMENTS.get(pd.getName());
+
+ String propName = pd.getName();
+ if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
+ throw new AssertionError("SETTING_ASSIGNMENTS doesn't yet contain \"" + propName + "\"");
+ }
+ Object newValue = SETTING_ASSIGNMENTS.get(propName);
pd.getWriteMethod().invoke(tc, newValue);
Template t = new Template(null, "", DEFAULT_CFG);
Method tReaderMethod = t.getClass().getMethod(pd.getReadMethod().getName());
- assertNotEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+ assertNotEquals("For \"" + propName + "\"", newValue, tReaderMethod.invoke(t));
tc.apply(t);
- assertEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
+ assertEquals("For \"" + propName + "\"", newValue, tReaderMethod.invoke(t));
}
}
@@ -705,6 +719,9 @@ public class TemplateConfigurationTest {
TemplateConfiguration tc = new TemplateConfiguration();
String propName = propDesc.getName();
+ if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
+ throw new AssertionError("SETTING_ASSIGNMENTS doesn't yet contain \"" + propName + "\"");
+ }
Object value = SETTING_ASSIGNMENTS.get(propName);
propDesc.getWriteMethod().invoke(tc, value);
@@ -905,8 +922,12 @@ public class TemplateConfigurationTest {
for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
TemplateConfiguration tc = new TemplateConfiguration();
checkAllIsSetFalseExcept(tc, null);
- pd.getWriteMethod().invoke(tc, SETTING_ASSIGNMENTS.get(pd.getName()));
- checkAllIsSetFalseExcept(tc, pd.getName());
+ String propName = pd.getName();
+ if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
+ throw new AssertionError("SETTING_ASSIGNMENTS doesn't yet contain \"" + propName + "\"");
+ }
+ pd.getWriteMethod().invoke(tc, SETTING_ASSIGNMENTS.get(propName));
+ checkAllIsSetFalseExcept(tc, propName);
}
}