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/09 00:34:16 UTC

incubator-freemarker git commit: TemplateXxxFormat and related API cleanup: Exceptions thrown. Introducing common ancestors for number-formatting-related and date-formatting-related classes

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3-gae 898331323 -> ef18a73e5


TemplateXxxFormat and related API cleanup: Exceptions thrown. Introducing common ancestors for number-formatting-related and date-formatting-related classes


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

Branch: refs/heads/2.3-gae
Commit: ef18a73e51942dd315126ae4343407f5b281a1a9
Parents: 8983313
Author: ddekany <dd...@apache.org>
Authored: Tue Sep 8 23:37:47 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Wed Sep 9 00:33:09 2015 +0200

----------------------------------------------------------------------
 .../core/BuiltInsForMultipleTypes.java          |  2 +-
 src/main/java/freemarker/core/Environment.java  | 55 ++++++++------------
 .../core/InvalidFormatStringException.java      |  2 +-
 src/main/java/freemarker/core/MessageUtil.java  |  2 +-
 .../freemarker/core/TemplateDateFormat.java     | 37 ++++++-------
 .../core/TemplateDateFormatFactory.java         | 23 ++++----
 .../freemarker/core/TemplateNumberFormat.java   | 20 +++----
 .../core/TemplateNumberFormatFactory.java       | 12 +++--
 .../freemarker/core/TemplateValueFormat.java    | 33 ++++++++++++
 .../core/TemplateValueFormatException.java      | 37 +++++++++++++
 .../core/TemplateValueFormatFactory.java        | 28 ++++++++++
 .../core/UnformattableDateException.java        |  5 +-
 .../core/UnformattableNumberException.java      |  2 +-
 ...nDateTypeFormattingUnsupportedException.java |  2 +-
 src/manual/book.xml                             | 25 +++++++--
 .../core/BaseNTemplateNumberFormatFactory.java  |  4 +-
 .../templates/dateformat-iso-like.ftl           | 10 ++--
 17 files changed, 201 insertions(+), 98 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
index 55fa6b3..58bcddc 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -563,7 +563,7 @@ class BuiltInsForMultipleTypes {
                             }
                         }
                         cachedValue = defaultFormat.format(dateModel);
-                    } catch (UnformattableDateException e) {
+                    } catch (TemplateValueFormatException e) {
                         throw MessageUtil.newCantFormatDateException(target, e);
                     }
                 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/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 1987353..29812a9 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1061,7 +1061,7 @@ public final class Environment extends Configurable {
             throws TemplateException {
         try {
             return format.format(number);
-        } catch (UnformattableNumberException e) {
+        } catch (TemplateValueFormatException e) {
             _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
                     "Failed to format number with format ", new _DelayedJQuote(format.getDescription()), ": ",
                     e.getMessage())
@@ -1099,7 +1099,7 @@ public final class Environment extends Configurable {
      * 
      * @since 2.3.24
      */
-    public TemplateNumberFormat getTemplateNumberFormat() throws InvalidFormatStringException {
+    public TemplateNumberFormat getTemplateNumberFormat() throws TemplateValueFormatException {
         TemplateNumberFormat format = cachedTemplateNumberFormat;
         if (format == null) {
             format = getTemplateNumberFormat(getNumberFormat(), false);
@@ -1120,7 +1120,7 @@ public final class Environment extends Configurable {
      * 
      * @since 2.3.24
      */
-    public TemplateNumberFormat getTemplateNumberFormat(String formatString) throws InvalidFormatStringException {
+    public TemplateNumberFormat getTemplateNumberFormat(String formatString) throws TemplateValueFormatException {
         return getTemplateNumberFormat(formatString, true);
     }
 
@@ -1143,7 +1143,7 @@ public final class Environment extends Configurable {
      * @since 2.3.24
      */
     public TemplateNumberFormat getTemplateNumberFormat(String formatString, Locale locale)
-            throws InvalidFormatStringException {
+            throws TemplateValueFormatException {
         if (locale.equals(getLocale())) {
             getTemplateNumberFormat(formatString);
         }
@@ -1158,7 +1158,7 @@ public final class Environment extends Configurable {
         TemplateNumberFormat format;
         try {
             format = getTemplateNumberFormat();
-        } catch (InvalidFormatStringException e) {
+        } catch (TemplateValueFormatException e) {
             _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
                     "Failed to get number format object for the current number format string, ",
                     new _DelayedJQuote(getNumberFormat()), ": ", e.getMessage())
@@ -1180,7 +1180,7 @@ public final class Environment extends Configurable {
         TemplateNumberFormat format;
         try {
             format = getTemplateNumberFormat(formatString);
-        } catch (InvalidFormatStringException e) {
+        } catch (TemplateValueFormatException e) {
             _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
                     "Failed to get number format object for the ", new _DelayedJQuote(formatString),
                     " number format string: ", e.getMessage())
@@ -1201,7 +1201,7 @@ public final class Environment extends Configurable {
      *            result from the cache regardless of this parameter.
      */
     private TemplateNumberFormat getTemplateNumberFormat(String formatString, boolean cacheResult)
-            throws InvalidFormatStringException {
+            throws TemplateValueFormatException {
         if (cachedTemplateNumberFormats == null) {
             if (cacheResult) {
                 cachedTemplateNumberFormats = new HashMap<String, TemplateNumberFormat>();
@@ -1231,7 +1231,7 @@ public final class Environment extends Configurable {
      *            Not {@code null}
      */
     private TemplateNumberFormat getTemplateNumberFormatWithoutCache(String formatString, Locale locale)
-            throws UndefinedCustomFormatException, InvalidFormatParametersException {
+            throws TemplateValueFormatException {
         int formatStringLen = formatString.length();
         if (formatStringLen > 1
                 && formatString.charAt(0) == '@'
@@ -1352,7 +1352,7 @@ public final class Environment extends Configurable {
         
         try {
             return format.format(tdm);
-        } catch (UnformattableDateException e) {
+        } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatDateException(tdmSourceExpr, e);
         }
     }
@@ -1375,7 +1375,7 @@ public final class Environment extends Configurable {
         
         try {
             return format.format(tdm);
-        } catch (UnformattableDateException e) {
+        } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatDateException(blamedDateSourceExp, e);
         }
     }
@@ -1392,10 +1392,8 @@ public final class Environment extends Configurable {
      *            The exact {@link Date} class, like {@link java.sql.Date} or {@link java.sql.Time}; this can influences
      *            time zone selection. See also: {@link #setSQLDateAndTimeTimeZone(TimeZone)}
      */
-    public TemplateDateFormat getTemplateDateFormat(
-            int dateType, Class<? extends Date> dateClass)
-                    throws UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
-                    InvalidFormatParametersException {
+    public TemplateDateFormat getTemplateDateFormat(int dateType, Class<? extends Date> dateClass)
+            throws TemplateValueFormatException {
         boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
         return getTemplateDateFormat(dateType, shouldUseSQLDTTimeZone(isSQLDateOrTime), isSQLDateOrTime);
     }
@@ -1418,8 +1416,7 @@ public final class Environment extends Configurable {
      */
     public TemplateDateFormat getTemplateDateFormat(
             String formatString, int dateType, Class<? extends Date> dateClass)
-                    throws UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
-                    InvalidFormatParametersException {
+                    throws TemplateValueFormatException {
         boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
         return getTemplateDateFormat(
                 formatString, dateType,
@@ -1447,8 +1444,7 @@ public final class Environment extends Configurable {
             String formatString,
             int dateType, Class<? extends Date> dateClass,
             Locale locale)
-                    throws UndefinedCustomFormatException, InvalidFormatParametersException,
-                    UnknownDateTypeFormattingUnsupportedException {
+                    throws TemplateValueFormatException {
         boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
         boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
         return getTemplateDateFormat(
@@ -1480,8 +1476,7 @@ public final class Environment extends Configurable {
             String formatString,
             int dateType, Class<? extends Date> dateClass,
             Locale locale, TimeZone timeZone, TimeZone sqlDateAndTimeTimeZone)
-                    throws UndefinedCustomFormatException, InvalidFormatParametersException,
-                    UnknownDateTypeFormattingUnsupportedException {
+                    throws TemplateValueFormatException {
         boolean isSQLDateOrTime = isSQLDateOrTimeClass(dateClass);
         boolean useSQLDTTZ = shouldUseSQLDTTimeZone(isSQLDateOrTime);
         return getTemplateDateFormat(
@@ -1526,8 +1521,7 @@ public final class Environment extends Configurable {
     public TemplateDateFormat getTemplateDateFormat(
             String formatString,
             int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput)
-                    throws UndefinedCustomFormatException, InvalidFormatParametersException,
-                    UnknownDateTypeFormattingUnsupportedException {
+                    throws TemplateValueFormatException {
         Locale currentLocale = getLocale();
         if (locale.equals(currentLocale)) {
             int equalCurrentTZ;
@@ -1560,7 +1554,7 @@ public final class Environment extends Configurable {
             return getTemplateDateFormat(dateType, dateClass);
         } catch (UnknownDateTypeFormattingUnsupportedException e) {
             throw MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e);
-        } catch (InvalidFormatStringException e) {
+        } catch (TemplateValueFormatException e) {
             String settingName;
             String settingValue;
             switch (dateType) {
@@ -1598,14 +1592,14 @@ public final class Environment extends Configurable {
             String formatString, int dateType, Class<? extends Date> dateClass,
             Expression blamedDateSourceExp, Expression blamedFormatterExp,
             boolean useTempModelExc)
-            throws TemplateException {
+                    throws TemplateException {
         try {
             return getTemplateDateFormat(formatString, dateType, dateClass);
         } catch (UnknownDateTypeFormattingUnsupportedException e) {
             throw MessageUtil.newCantFormatUnknownTypeDateException(blamedDateSourceExp, e);
-        } catch (InvalidFormatStringException e) {
+        } catch (TemplateValueFormatException e) {
             _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                    "Malformed date/time/datetime format string: ",
+                    "Can't create date/time/datetime format based on format string ",
                     new _DelayedJQuote(formatString), ". Reason given: ",
                     e.getMessage())
                     .blame(blamedFormatterExp);
@@ -1619,8 +1613,7 @@ public final class Environment extends Configurable {
      * of some if the parameters.
      */
     private TemplateDateFormat getTemplateDateFormat(int dateType, boolean useSQLDTTZ, boolean zonelessInput)
-                    throws UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
-                    InvalidFormatParametersException {
+            throws TemplateValueFormatException {
         if (dateType == TemplateDateModel.UNKNOWN) {
             throw new UnknownDateTypeFormattingUnsupportedException();
         }
@@ -1666,8 +1659,7 @@ public final class Environment extends Configurable {
     private TemplateDateFormat getTemplateDateFormat(
             String formatString, int dateType, boolean useSQLDTTimeZone, boolean zonelessInput,
             boolean cacheResult)
-                    throws UnknownDateTypeFormattingUnsupportedException, UndefinedCustomFormatException,
-                    InvalidFormatParametersException {
+                    throws TemplateValueFormatException {
         HashMap<String, TemplateDateFormat> cachedFormatsByFormatString;
         readFromCache: do {
             HashMap<String, TemplateDateFormat>[] cachedTempDateFormatsByFmtStrArray = this.cachedTempDateFormatsByFmtStrArray;
@@ -1729,8 +1721,7 @@ public final class Environment extends Configurable {
      */
     private TemplateDateFormat getTemplateDateFormatWithoutCache(
             String formatString, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput)
-                    throws UndefinedCustomFormatException, InvalidFormatParametersException,
-                    UnknownDateTypeFormattingUnsupportedException {
+                    throws TemplateValueFormatException {
         final int formatStringLen = formatString.length();
         final String formatParams;
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/InvalidFormatStringException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/InvalidFormatStringException.java b/src/main/java/freemarker/core/InvalidFormatStringException.java
index 56a4ce5..ca71dc5 100644
--- a/src/main/java/freemarker/core/InvalidFormatStringException.java
+++ b/src/main/java/freemarker/core/InvalidFormatStringException.java
@@ -24,7 +24,7 @@ package freemarker.core;
  * 
  * @since 2.3.24
  */
-public abstract class InvalidFormatStringException extends Exception {
+public abstract class InvalidFormatStringException extends TemplateValueFormatException {
     
     public InvalidFormatStringException(String message, Throwable cause) {
         super(message, cause);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/MessageUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/MessageUtil.java b/src/main/java/freemarker/core/MessageUtil.java
index c355c79..ad0827a 100644
--- a/src/main/java/freemarker/core/MessageUtil.java
+++ b/src/main/java/freemarker/core/MessageUtil.java
@@ -293,7 +293,7 @@ class MessageUtil {
     }
 
     static TemplateModelException newCantFormatDateException(
-            Expression dateSourceExpr, UnformattableDateException cause) {
+            Expression dateSourceExpr, TemplateValueFormatException cause) {
         return new _TemplateModelException(cause, null, new _ErrorDescriptionBuilder(
                 cause.getMessage())
                 .blame(dateSourceExpr));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/TemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateDateFormat.java b/src/main/java/freemarker/core/TemplateDateFormat.java
index d39c3e7..497ea25 100644
--- a/src/main/java/freemarker/core/TemplateDateFormat.java
+++ b/src/main/java/freemarker/core/TemplateDateFormat.java
@@ -39,22 +39,24 @@ import freemarker.template.TemplateModelException;
  * 
  * @since 2.3.24
  */
-public abstract class TemplateDateFormat {
+public abstract class TemplateDateFormat extends TemplateValueFormat {
     
     /**
-     * @param dateModel The date/time/dateTime to format. Most implementations will just work with the return value of
-     *          {@link TemplateDateModel#getAsDate()}, but some may format differently depending on the properties of
-     *          a custom {@link TemplateDateModel} implementation.
-     *          
+     * @param dateModel
+     *            The date/time/dateTime to format. Most implementations will just work with the return value of
+     *            {@link TemplateDateModel#getAsDate()}, but some may format differently depending on the properties of
+     *            a custom {@link TemplateDateModel} implementation.
+     * 
      * @return The date/time/dateTime as text, with no escaping (like no HTML escaping). Can't be {@code null}.
      * 
-     * @throws UnformattableDateException When a {@link TemplateDateModel} can't be formatted because of the
-     *           value/properties of the {@link TemplateDateModel}. The most often used subclass is
-     *           {@link UnknownDateTypeFormattingUnsupportedException}. 
-     * @throws TemplateModelException Exception thrown by the {@code dateModel} object when calling its methods.  
+     * @throws TemplateValueFormatException
+     *             When a problem occurs during the formatting of the value. Notable subclass:
+     *             {@link UnknownDateTypeFormattingUnsupportedException}
+     * @throws TemplateModelException
+     *             Exception thrown by the {@code dateModel} object when calling its methods.
      */
     public abstract String format(TemplateDateModel dateModel)
-            throws UnformattableDateException, TemplateModelException;
+            throws TemplateValueFormatException, TemplateModelException;
 
     /**
      * <b>[Not yet used, might changes in 2.3.24 final]</b>
@@ -62,10 +64,10 @@ public abstract class TemplateDateFormat {
      * FreeMarker call {@link #format(TemplateDateModel)} and escape its result. If the markup format would be just the
      * result of {@link #format(TemplateDateModel)} escaped, it should return {@code null}.
      */
-    public abstract <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-            MarkupOutputFormat<MO> outputFormat)
-                    throws UnformattableNumberException, TemplateModelException;
-    
+    public abstract <MO extends TemplateMarkupOutputModel> MO format(
+            TemplateDateModel dateModel, MarkupOutputFormat<MO> outputFormat)
+                    throws TemplateValueFormatException, TemplateModelException;
+
     /**
      * <b>[Not yet used, might changes in 2.3.24 final]</b>
      * Same as {@link #format(TemplateDateModel, MarkupOutputFormat)}, but prints the result to a {@link Writer}
@@ -79,7 +81,7 @@ public abstract class TemplateDateFormat {
      */
     public <MO extends TemplateMarkupOutputModel> boolean format(TemplateDateModel dateModel,
             MarkupOutputFormat<MO> outputFormat, Writer out)
-                    throws UnformattableNumberException, TemplateModelException, IOException {
+                    throws TemplateValueFormatException, TemplateModelException, IOException {
         MO mo = format(dateModel, outputFormat);
         if (mo == null) {
             return false;
@@ -97,11 +99,6 @@ public abstract class TemplateDateFormat {
      * @return The interpretation of the text as {@link Date}. Can't be {@code null}.
      */
     public abstract Date parse(String s) throws java.text.ParseException;
-
-    /**
-     * Meant to be used in error messages to tell what format the parsed string didn't fit.
-     */
-    public abstract String getDescription();
     
     /**
      * Tells if this formatter should be re-created if the locale changes.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/TemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateDateFormatFactory.java b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
index e9fb2fe..cf7aad5 100644
--- a/src/main/java/freemarker/core/TemplateDateFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateDateFormatFactory.java
@@ -27,12 +27,12 @@ import freemarker.template.Configuration;
 import freemarker.template.TemplateDateModel;
 
 /**
- * Factory for a certain type of date/time/dateTime formatting ({@link TemplateDateFormat}). Usually a singleton
+ * Factory for a certain kind of date/time/dateTime formatting ({@link TemplateDateFormat}). Usually a singleton
  * (one-per-VM or one-per-{@link Configuration}), and so must be thread-safe.
  * 
  * @since 2.3.24
  */
-public abstract class TemplateDateFormatFactory {
+public abstract class TemplateDateFormatFactory extends TemplateValueFormatFactory {
     
     /**
      * Returns a formatter for the given parameters.
@@ -55,37 +55,36 @@ public abstract class TemplateDateFormatFactory {
      *            which case the method should throw an {@link UnknownDateTypeFormattingUnsupportedException} 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} shouldn't be followed).
+     *            forever (i.e. locale changes in the {@link Environment} must not be followed).
      * @param timeZone
      *            The time zone to format for. Not {@code null}. The resulting format should be bound to this time zone
-     *            forever (i.e. time zone changes in the {@link Environment} shouldn't be followed).
+     *            forever (i.e. time zone changes in the {@link Environment} must not be followed).
      * @param zonelessInput
      *            Indicates that the input Java {@link Date} is not from a time zone aware source. When this is
      *            {@code true}, the formatters shouldn't override the time zone provided to its constructor (most
      *            formatters don't do that anyway), and it shouldn't show the time zone, if it can hide it (like a
      *            {@link SimpleDateFormat} pattern-based formatter may can't do that, as the pattern prescribes what to
      *            show).
-     * 
      *            <p>
      *            As of FreeMarker 2.3.21, this is {@code true} exactly when the date is an SQL "date without time of
      *            the day" (i.e., a {@link java.sql.Date java.sql.Date}) or an SQL "time of the day" value (i.e., a
      *            {@link java.sql.Time java.sql.Time}, although this rule can change in future, depending on
-     *            configuration settings and such, so you should rely on this rule, just accept what this parameter
+     *            configuration settings and such, so you shouldn't rely on this rule, just accept what this parameter
      *            says.
      * @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)}.
      * 
-     * @throws InvalidFormatParametersException
-     *             if the {@code params} is malformed
-     * @throws UnknownDateTypeFormattingUnsupportedException
-     *             if {@code dateType} is {@link TemplateDateModel#UNKNOWN}, and that's unsupported by the formatter
-     *             implementation.
+     * @throws TemplateValueFormatException
+     *             If any problem occurs while parsing/getting the format. Notable subclasses:
+     *             {@link InvalidFormatParametersException} if {@code params} is malformed;
+     *             {@link UnknownDateTypeFormattingUnsupportedException} if {@code dateType} is
+     *             {@link TemplateDateModel#UNKNOWN} and that's unsupported by this factory.
      */
     public abstract TemplateDateFormat get(
             String params,
             int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput,
             Environment env)
-                    throws UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException;
+                    throws TemplateValueFormatException;
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/TemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateNumberFormat.java b/src/main/java/freemarker/core/TemplateNumberFormat.java
index 10f14aa..87d4bbc 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -38,7 +38,7 @@ import freemarker.template.TemplateNumberModel;
  * 
  * @since 2.3.24
  */
-public abstract class TemplateNumberFormat {
+public abstract class TemplateNumberFormat extends TemplateValueFormat {
 
     /**
      * @param numberModel
@@ -48,15 +48,14 @@ public abstract class TemplateNumberFormat {
      * 
      * @return The date/time/dateTime as text, with no escaping (like no HTML escaping). Can't be {@code null}.
      * 
-     * @throws UnformattableNumberException
-     *             When a {@link TemplateDateModel} can't be formatted because of the value/properties of the
-     *             {@link TemplateDateModel}. The most often used subclass is
-     *             {@link UnknownDateTypeFormattingUnsupportedException}.
+     * @throws TemplateValueFormatException
+     *             If any problem occurs while parsing/getting the format. Notable subclass:
+     *             {@link UnformattableNumberException}.
      * @throws TemplateModelException
      *             Exception thrown by the {@code dateModel} object when calling its methods.
      */
     public abstract String format(TemplateNumberModel numberModel)
-            throws UnformattableNumberException, TemplateModelException;
+            throws TemplateValueFormatException, TemplateModelException;
 
     /**
      * <b>[Not yet used, might changes in 2.3.24 final]</b>
@@ -66,7 +65,7 @@ public abstract class TemplateNumberFormat {
      */
     public abstract <MO extends TemplateMarkupOutputModel> MO format(
             TemplateNumberModel dateModel, MarkupOutputFormat<MO> outputFormat)
-                    throws UnformattableNumberException, TemplateModelException;
+                    throws TemplateValueFormatException, TemplateModelException;
     
     /**
      * <b>[Not yet used, might changes in 2.3.24 final]</b>
@@ -81,7 +80,7 @@ public abstract class TemplateNumberFormat {
      */
     public <MO extends TemplateMarkupOutputModel> boolean format(
             TemplateNumberModel dateModel, MarkupOutputFormat<MO> outputFormat, Writer out)
-                    throws UnformattableNumberException, TemplateModelException, IOException {
+                    throws TemplateValueFormatException, TemplateModelException, IOException {
         MO mo = format(dateModel, outputFormat);
         if (mo == null) {
             return false;
@@ -91,11 +90,6 @@ public abstract class TemplateNumberFormat {
     }
 
     /**
-     * Meant to be used in error messages to tell what format the parsed string didn't fit.
-     */
-    public abstract String getDescription();
-    
-    /**
      * Tells if this formatter should be re-created if the locale changes.
      */
     public abstract boolean isLocaleBound();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateNumberFormatFactory.java b/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
index ddbf74a..90e84e0 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormatFactory.java
@@ -23,12 +23,12 @@ import java.util.Locale;
 import freemarker.template.Configuration;
 
 /**
- * Factory for a certain type of number formatting ({@link TemplateNumberFormat}). Usually a singleton (one-per-VM or
+ * Factory for a certain kind of number formatting ({@link TemplateNumberFormat}). Usually a singleton (one-per-VM or
  * one-per-{@link Configuration}), and so must be thread-safe.
  * 
  * @since 2.3.24
  */
-public abstract class TemplateNumberFormatFactory {
+public abstract class TemplateNumberFormatFactory extends TemplateValueFormatFactory {
 
     /**
      * Returns a formatter for the given parameters.
@@ -47,12 +47,16 @@ public abstract class TemplateNumberFormatFactory {
      *            {@link TemplateNumberFormatFactory} implementation. Not {@code null}, often an empty string.
      * @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} shouldn't be followed).
+     *            forever (i.e. locale changes in the {@link Environment} must not be followed).
      * @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)}.
+     *            
+     * @throws TemplateValueFormatException
+     *             if any problem occurs while parsing/getting the format. Notable subclasses:
+     *             {@link InvalidFormatParametersException} if the {@code params} is malformed.
      */
     public abstract TemplateNumberFormat get(String params, Locale locale, Environment env)
-            throws InvalidFormatParametersException;
+            throws TemplateValueFormatException;
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/TemplateValueFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateValueFormat.java b/src/main/java/freemarker/core/TemplateValueFormat.java
new file mode 100644
index 0000000..433f652
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateValueFormat.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.core;
+
+/**
+ * Superclass of all value format objects; objects that convert values to strings, or parse strings.
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateValueFormat {
+
+    /**
+     * Meant to be used in error messages to tell what format the parsed string didn't fit.
+     */
+    public abstract String getDescription();
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/TemplateValueFormatException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateValueFormatException.java b/src/main/java/freemarker/core/TemplateValueFormatException.java
new file mode 100644
index 0000000..2e26ee0
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateValueFormatException.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.core;
+
+/**
+ * Error while getting, creating or applying {@link TemplateValueFormat}-s (including its subclasses, like
+ * {@link TemplateNumberFormat}).
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateValueFormatException extends Exception {
+
+    public TemplateValueFormatException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public TemplateValueFormatException(String message) {
+        super(message);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/TemplateValueFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateValueFormatFactory.java b/src/main/java/freemarker/core/TemplateValueFormatFactory.java
new file mode 100644
index 0000000..f57ea26
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateValueFormatFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ * 
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ * 
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package freemarker.core;
+
+/**
+ * Superclass of all format factories.
+ * 
+ * @since 2.3.24
+ */
+public abstract class TemplateValueFormatFactory {
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/UnformattableDateException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnformattableDateException.java b/src/main/java/freemarker/core/UnformattableDateException.java
index a91df4f..8173b30 100644
--- a/src/main/java/freemarker/core/UnformattableDateException.java
+++ b/src/main/java/freemarker/core/UnformattableDateException.java
@@ -23,11 +23,12 @@ import freemarker.template.TemplateDateModel;
 
 /**
  * Thrown when a {@link TemplateDateModel} can't be formatted because of the value/properties of the
- * {@link TemplateDateModel}.  The most often used subclass is {@link UnknownDateTypeFormattingUnsupportedException}.
+ * {@link TemplateDateModel}. For example, a formatter may not support dates before year 1. The most often used subclass
+ * is {@link UnknownDateTypeFormattingUnsupportedException}.
  * 
  * @since 2.3.24
  */
-public abstract class UnformattableDateException extends Exception {
+public abstract class UnformattableDateException extends TemplateValueFormatException {
 
     public UnformattableDateException(String message, Throwable cause) {
         super(message, cause);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/UnformattableNumberException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnformattableNumberException.java b/src/main/java/freemarker/core/UnformattableNumberException.java
index b3efec8..2342338 100644
--- a/src/main/java/freemarker/core/UnformattableNumberException.java
+++ b/src/main/java/freemarker/core/UnformattableNumberException.java
@@ -28,7 +28,7 @@ import freemarker.template.TemplateNumberModel;
  * 
  * @since 2.3.24
  */
-public class UnformattableNumberException extends Exception {
+public class UnformattableNumberException extends TemplateValueFormatException {
 
     public UnformattableNumberException(String message, Throwable cause) {
         super(message, cause);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/main/java/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java b/src/main/java/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
index 360b678..45be76a 100644
--- a/src/main/java/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
+++ b/src/main/java/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
@@ -22,7 +22,7 @@ package freemarker.core;
 import freemarker.template.TemplateDateModel;
 
 /**
- * Thrown when a {@link TemplateDateModel} can't be formatted because it's type is {@link TemplateDateModel#UNKNOWN}.
+ * Thrown when a {@link TemplateDateModel} can't be formatted because its type is {@link TemplateDateModel#UNKNOWN}.
  * 
  * @since 2.3.24
  */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index cf7416d..5524684 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -25673,9 +25673,7 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   <literal>getTemplateNumberFormat(<replaceable>...</replaceable>)</literal>
                   and
                   <literal>getTemplateDateFormat(<replaceable>...</replaceable>)</literal>
-                  variations. (Only since RC1: public
-                  <literal>TemplateDateFormat</literal> returning
-                  methods)</para>
+                  variations.</para>
                 </listitem>
 
                 <listitem>
@@ -25812,6 +25810,27 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
           </itemizedlist>
         </section>
+
+        <section>
+          <title>Changes compared to 2.3.24 Preview 1</title>
+
+          <itemizedlist>
+            <listitem>
+              <para><literal>Environment</literal> now has public
+              <literal>TemplateDateFormat</literal> returning methods.</para>
+            </listitem>
+
+            <listitem>
+              <para>The various
+              <literal>Template<replaceable>Xxx</replaceable>Format</literal>
+              and
+              <literal>Template<replaceable>Xxx</replaceable>FormatFacotry</literal>
+              exceptions were united under a common abstract superclass,
+              <literal>TemplateValueFormatException</literal>, which is now
+              what the methods of said classes throw (mostly).</para>
+            </listitem>
+          </itemizedlist>
+        </section>
       </section>
 
       <section xml:id="versions_2_3_23">

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
index 09528ec..f2f0dae 100644
--- a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
@@ -44,7 +44,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
                 params = params.substring(0, barIdx);
                 try {
                     fallbackFormat = env.getTemplateNumberFormat(fallbackFormatStr, locale);
-                } catch (InvalidFormatStringException e) {
+                } catch (TemplateValueFormatException e) {
                     throw new InvalidFormatParametersException(
                             "Couldn't get the fallback number format (specified after the \"|\"), "
                             + StringUtil.jQuote(fallbackFormatStr) + ". Reason: " + e.getMessage(),
@@ -81,7 +81,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
         
         @Override
         public String format(TemplateNumberModel numberModel)
-                throws UnformattableNumberException, TemplateModelException {
+                throws TemplateModelException, TemplateValueFormatException {
             Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
             try {
                 return Integer.toString(NumberUtil.toIntExact(n), base);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef18a73e/src/test/resources/freemarker/test/templatesuite/templates/dateformat-iso-like.ftl
----------------------------------------------------------------------
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/dateformat-iso-like.ftl b/src/test/resources/freemarker/test/templatesuite/templates/dateformat-iso-like.ftl
index 5dfdee7..3e2d1c2 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/dateformat-iso-like.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/dateformat-iso-like.ftl
@@ -130,8 +130,8 @@
 
 <@assertFails message="Use ?date, ?time, or ?datetime">${unknownDate?string.xs}</@>
 <@assertFails message="Use ?date, ?time, or ?datetime">${unknownDate?string.iso}</@>
-<@assertFails message="malformed">${.now?string.xs_fz_nz}</@>
-<@assertFails message="malformed">${.now?string.xs_u_fu}</@>
-<@assertFails message="malformed">${.now?string.xs_s_ms}</@>
-<@assertFails message="malformed">${.now?string.xs_q}</@>
-<@assertFails message="malformed">${.now?string.xss}</@>
\ No newline at end of file
+<@assertFails message="format string">${.now?string.xs_fz_nz}</@>
+<@assertFails message="format string">${.now?string.xs_u_fu}</@>
+<@assertFails message="format string">${.now?string.xs_s_ms}</@>
+<@assertFails message="format string">${.now?string.xs_q}</@>
+<@assertFails message="format string">${.now?string.xss}</@>
\ No newline at end of file