http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java 
b/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java
deleted file mode 100644
index e73e2ec..0000000
--- 
a/src/main/java/org/apache/freemarker/core/JavaTemplateNumberFormatFactory.java
+++ /dev/null
@@ -1,120 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.text.NumberFormat;
-import java.text.ParseException;
-import java.util.Locale;
-import java.util.concurrent.ConcurrentHashMap;
-
-import org.slf4j.Logger;
-
-/**
- * Deals with {@link TemplateNumberFormat}-s that just wrap a Java {@link 
NumberFormat}.
- */
-class JavaTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
-    
-    static final JavaTemplateNumberFormatFactory INSTANCE = new 
JavaTemplateNumberFormatFactory();
-    
-    private static final Logger LOG = _CoreLogs.RUNTIME;
-
-    private static final ConcurrentHashMap<CacheKey, NumberFormat> 
GLOBAL_FORMAT_CACHE
-            = new ConcurrentHashMap<>();
-    private static final int LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE = 1024;
-    
-    private JavaTemplateNumberFormatFactory() {
-        // Not meant to be instantiated
-    }
-    
-    @Override
-    public TemplateNumberFormat get(String params, Locale locale, Environment 
env)
-            throws InvalidFormatParametersException {
-        CacheKey cacheKey = new CacheKey(params, locale);
-        NumberFormat jFormat = GLOBAL_FORMAT_CACHE.get(cacheKey);
-        if (jFormat == null) {
-            if ("number".equals(params)) {
-                jFormat = NumberFormat.getNumberInstance(locale);
-            } else if ("currency".equals(params)) {
-                jFormat = NumberFormat.getCurrencyInstance(locale);
-            } else if ("percent".equals(params)) {
-                jFormat = NumberFormat.getPercentInstance(locale);
-            } else if ("computer".equals(params)) {
-                jFormat = env.getCNumberFormat();
-            } else {
-                try {
-                    jFormat = ExtendedDecimalFormatParser.parse(params, 
locale);
-                } catch (ParseException e) {
-                    String msg = e.getMessage();
-                    throw new InvalidFormatParametersException(
-                            msg != null ? msg : "Invalid DecimalFormat 
pattern", e);
-                }
-            }
-
-            if (GLOBAL_FORMAT_CACHE.size() >= 
LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE) {
-                boolean triggered = false;
-                synchronized (JavaTemplateNumberFormatFactory.class) {
-                    if (GLOBAL_FORMAT_CACHE.size() >= 
LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE) {
-                        triggered = true;
-                        GLOBAL_FORMAT_CACHE.clear();
-                    }
-                }
-                if (triggered) {
-                    LOG.warn("Global Java NumberFormat cache has exceeded {} 
entries => cache flushed. "
-                            + "Typical cause: Some template generates high 
variety of format pattern strings.",
-                            LEAK_ALERT_NUMBER_FORMAT_CACHE_SIZE);
-                }
-            }
-            
-            NumberFormat prevJFormat = 
GLOBAL_FORMAT_CACHE.putIfAbsent(cacheKey, jFormat);
-            if (prevJFormat != null) {
-                jFormat = prevJFormat;
-            }
-        }  // if cache miss
-        
-        // JFormat-s aren't thread-safe; must clone it
-        jFormat = (NumberFormat) jFormat.clone();
-        
-        return new JavaTemplateNumberFormat(jFormat, params); 
-    }
-
-    private static final class CacheKey {
-        private final String pattern;
-        private final Locale locale;
-
-        CacheKey(String pattern, Locale locale) {
-            this.pattern = pattern;
-            this.locale = locale;
-        }
-
-        @Override
-        public boolean equals(Object o) {
-            if (o instanceof CacheKey) {
-                CacheKey fk = (CacheKey) o;
-                return fk.pattern.equals(pattern) && fk.locale.equals(locale);
-            }
-            return false;
-        }
-
-        @Override
-        public int hashCode() {
-            return pattern.hashCode() ^ locale.hashCode();
-        }
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/MessageUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/MessageUtil.java 
b/src/main/java/org/apache/freemarker/core/MessageUtil.java
index c0f6a96..4e658db 100644
--- a/src/main/java/org/apache/freemarker/core/MessageUtil.java
+++ b/src/main/java/org/apache/freemarker/core/MessageUtil.java
@@ -24,6 +24,10 @@ import java.util.ArrayList;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelException;
 import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+import 
org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException;
 
 /**
  * Utilities for creating error messages (and other messages).
@@ -34,13 +38,9 @@ class MessageUtil {
             = "Can't convert the date-like value to string because it isn't "
               + "known if it's a date (no time part), time or date-time 
value.";
     
-    static final String UNKNOWN_DATE_PARSING_ERROR_MESSAGE
-            = "Can't parse the string to date-like value because it isn't "
-              + "known if it's desired result should be a date (no time part), 
a time, or a date-time value.";
-
-    static final String UNKNOWN_DATE_TYPE_ERROR_TIP = 
+    static final String UNKNOWN_DATE_TYPE_ERROR_TIP =
             "Use ?date, ?time, or ?datetime to tell FreeMarker the exact 
type.";
-    
+
     static final Object[] UNKNOWN_DATE_TO_STRING_TIPS = {
             UNKNOWN_DATE_TYPE_ERROR_TIP,
             "If you need a particular format only once, use ?string(pattern), 
like ?string('dd.MM.yyyy HH:mm:ss'), "
@@ -53,7 +53,7 @@ class MessageUtil {
 
     // Can't be instantiated
     private MessageUtil() { }
-        
+
     static String formatLocationForSimpleParsingError(Template template, int 
line, int column) {
         return formatLocation("in", template, line, column);
     }
@@ -78,7 +78,7 @@ class MessageUtil {
         Template t = macro.getTemplate();
         return formatLocation("at", t != null ? t.getSourceName() : null, 
macro.getName(), macro.isFunction(), line, column);
     }
-    
+
     static String formatLocationForEvaluationError(String templateSourceName, 
int line, int column) {
         return formatLocation("at", templateSourceName, line, column);
     }
@@ -86,7 +86,7 @@ class MessageUtil {
     private static String formatLocation(String preposition, Template 
template, int line, int column) {
         return formatLocation(preposition, template != null ? 
template.getSourceName() : null, line, column);
     }
-    
+
     private static String formatLocation(String preposition, String 
templateSourceName, int line, int column) {
         return formatLocation(
                 preposition, templateSourceName,
@@ -102,7 +102,7 @@ class MessageUtil {
         if (line < 0) {
             templateDesc = "?eval-ed string";
             macroOrFuncName = null;
-        } else { 
+        } else {
             templateDesc = templateSourceName != null
                 ? "template " + _StringUtil.jQuoteNoXSS(templateSourceName)
                 : "nameless template";
@@ -114,7 +114,7 @@ class MessageUtil {
               + " "
               + preposition + " " + formatPosition(line, column);
     }
-    
+
     static String formatPosition(int line, int column) {
         return "line " + (line >= 0 ? line : line - 
(ASTNode.RUNTIME_EVAL_LINE_DISPLACEMENT - 1))
                 + ", column " + column;
@@ -123,11 +123,11 @@ class MessageUtil {
     /**
      * Returns a single line string that is no longer than {@code maxLength}.
      * If will truncate the string at line-breaks too.
-     * The truncation is always signaled with a a {@code "..."} at the end of 
the result string.  
+     * The truncation is always signaled with a a {@code "..."} at the end of 
the result string.
      */
     static String shorten(String s, int maxLength) {
         if (maxLength < 5) maxLength = 5;
-        
+
         boolean isTruncated = false;
         
         int brIdx = s.indexOf('\n');
@@ -286,7 +286,7 @@ class MessageUtil {
                 "Instantiating ", className, " is not allowed in the template 
for security reasons.");
     }
     
-    static _TemplateModelException newCantFormatUnknownTypeDateException(
+    static TemplateModelException newCantFormatUnknownTypeDateException(
             ASTExpression dateSourceExpr, 
UnknownDateTypeFormattingUnsupportedException cause) {
         return new _TemplateModelException(cause, null, new 
_ErrorDescriptionBuilder(
                 MessageUtil.UNKNOWN_DATE_TO_STRING_ERROR_MESSAGE)
@@ -295,7 +295,7 @@ class MessageUtil {
     }
 
     static TemplateException newCantFormatDateException(TemplateDateFormat 
format, ASTExpression dataSrcExp,
-            TemplateValueFormatException e, boolean useTempModelExc) {
+                                                        
TemplateValueFormatException e, boolean useTempModelExc) {
         _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
                 "Failed to format date/time/datetime with format ", new 
_DelayedJQuote(format.getDescription()), ": ",
                 e.getMessage())
@@ -306,7 +306,7 @@ class MessageUtil {
     }
     
     static TemplateException newCantFormatNumberException(TemplateNumberFormat 
format, ASTExpression dataSrcExp,
-            TemplateValueFormatException e, boolean useTempModelExc) {
+                                                          
TemplateValueFormatException e, boolean useTempModelExc) {
         _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
                 "Failed to format number with format ", new 
_DelayedJQuote(format.getDescription()), ": ",
                 e.getMessage())

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java 
b/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java
deleted file mode 100644
index bac087a..0000000
--- a/src/main/java/org/apache/freemarker/core/ParsingNotSupportedException.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-/**
- * Thrown when the {@link TemplateValueFormat} doesn't support parsing, and 
parsing was invoked.
- * 
- * @since 2.3.24
- */
-public class ParsingNotSupportedException extends TemplateValueFormatException 
{
-
-    public ParsingNotSupportedException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public ParsingNotSupportedException(String message) {
-        this(message, null);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java 
b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
index 0a01c14..aaea96e 100644
--- a/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
+++ b/src/main/java/org/apache/freemarker/core/TemplateConfiguration.java
@@ -30,6 +30,8 @@ import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.outputformat.OutputFormat;
 import 
org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver;
 import org.apache.freemarker.core.util._NullArgumentException;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
 
 /**
  * Used for customizing the configuration settings for individual {@link 
Template}-s (or rather groups of templates),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java 
b/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java
deleted file mode 100644
index ed7f996..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateDateFormat.java
+++ /dev/null
@@ -1,109 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.text.DateFormat;
-import java.util.Date;
-
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-
-/**
- * Represents a date/time/dateTime format; used in templates for formatting 
and parsing with that format. This is
- * similar to Java's {@link DateFormat}, but made to fit the requirements of 
FreeMarker. Also, it makes easier to define
- * formats that can't be represented with Java's existing {@link DateFormat} 
implementations.
- * 
- * <p>
- * Implementations need not be thread-safe if the {@link 
TemplateNumberFormatFactory} 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.
- * 
- * @since 2.3.24
- */
-public abstract class TemplateDateFormat extends TemplateValueFormat {
-    
-    /**
-     * @param dateModel
-     *            The date/time/dateTime to format; not {@code null}. 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 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 formatToPlainText(TemplateDateModel dateModel)
-            throws TemplateValueFormatException, TemplateModelException;
-
-    /**
-     * Formats the model to markup instead of to plain text if the result 
markup will be more than just plain text
-     * escaped, otherwise falls back to formatting to plain text. If the 
markup result would be just the result of
-     * {@link #formatToPlainText(TemplateDateModel)} escaped, it must return 
the {@link String} that
-     * {@link #formatToPlainText(TemplateDateModel)} does.
-     * 
-     * <p>The implementation in {@link TemplateDateFormat} simply calls {@link 
#formatToPlainText(TemplateDateModel)}.
-     * 
-     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not 
{@code null}.
-     */
-    public Object format(TemplateDateModel dateModel) throws 
TemplateValueFormatException, TemplateModelException {
-        return formatToPlainText(dateModel);
-    }
-
-    /**
-     * Parsers a string to date/time/datetime, according to this format. Some 
format implementations may throw
-     * {@link ParsingNotSupportedException} here. 
-     * 
-     * @param s
-     *            The string to parse
-     * @param dateType
-     *            The expected date type of the result. Not all {@link 
TemplateDateFormat}-s will care about this;
-     *            though those who return a {@link TemplateDateModel} instead 
of {@link Date} often will. When strings
-     *            are parsed via {@code ?date}, {@code ?time}, or {@code 
?datetime}, then this parameter is
-     *            {@link TemplateDateModel#DATE}, {@link 
TemplateDateModel#TIME}, or {@link TemplateDateModel#DATETIME},
-     *            respectively. This parameter rarely if ever {@link 
TemplateDateModel#UNKNOWN}, but the implementation
-     *            that cares about this parameter should be prepared for that. 
If nothing else, it should throw
-     *            {@link UnknownDateTypeParsingUnsupportedException} then.
-     * 
-     * @return The interpretation of the text either as a {@link Date} or 
{@link TemplateDateModel}. Typically, a
-     *         {@link Date}. {@link TemplateDateModel} is used if you have to 
attach some application-specific
-     *         meta-information thats also extracted during {@link 
#formatToPlainText(TemplateDateModel)} (so if you format
-     *         something and then parse it, you get back an equivalent 
result). It can't be {@code null}. Known issue
-     *         (at least in FTL 2): {@code ?date}/{@code ?time}/{@code 
?datetime}, when not invoked as a method, can't
-     *         return the {@link TemplateDateModel}, only the {@link Date} 
from inside it, hence the additional
-     *         application-specific meta-info will be lost.
-     */
-    public abstract Object parse(String s, int dateType) throws 
TemplateValueFormatException;
-    
-    /**
-     * Tells if this formatter should be re-created if the locale changes.
-     */
-    public abstract boolean isLocaleBound();
-
-    /**
-     * Tells if this formatter should be re-created if the time zone changes. 
Currently always {@code true}.
-     */
-    public abstract boolean isTimeZoneBound();
-        
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java 
b/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java
deleted file mode 100644
index 6a6fb14..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateDateFormatFactory.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.text.SimpleDateFormat;
-import java.util.Date;
-import java.util.Locale;
-import java.util.TimeZone;
-
-import org.apache.freemarker.core.model.TemplateDateModel;
-
-/**
- * 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.
- * 
- * @see Configurable#setCustomDateFormats(java.util.Map)
- * 
- * @since 2.3.24
- */
-public abstract class TemplateDateFormatFactory extends 
TemplateValueFormatFactory {
-    
-    /**
-     * Returns a formatter for the given parameters.
-     * 
-     * <p>
-     * The returned formatter can be a new instance or a reused (cached) 
instance. Note that {@link Environment} itself
-     * caches the returned instances, though that cache is lost with the 
{@link Environment} (i.e., when the top-level
-     * template execution ends), also it might flushes lot of entries if the 
locale or time zone is changed during
-     * template execution. So caching on the factory level is still useful, 
unless creating the formatters is
-     * sufficiently cheap.
-     * 
-     * @param params
-     *            The string that further describes how the format should 
look. For example, when the
-     *            {@link Configurable#getDateFormat() dateFormat} 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 TemplateDateFormatFactory} implementation. Not {@code 
null}, often an empty string.
-     * @param dateType
-     *            {@link TemplateDateModel#DATE}, {@link 
TemplateDateModel#TIME}, {@link TemplateDateModel#DATETIME} or
-     *            {@link TemplateDateModel#UNKNOWN}. Supporting {@link 
TemplateDateModel#UNKNOWN} is not necessary, in
-     *            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} must 
not be followed).
-     * @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).
-     * @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 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 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 TemplateValueFormatException;
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java 
b/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java
deleted file mode 100644
index c43d7f2..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateFormatUtil.java
+++ /dev/null
@@ -1,76 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.util.Date;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-
-/**
- * Utility classes for implementing {@link TemplateValueFormat}-s.
- * 
- * @since 2.3.24 
- */
-public final class TemplateFormatUtil {
-    
-    private TemplateFormatUtil() {
-        // Not meant to be instantiated
-    }
-
-    public static void checkHasNoParameters(String params) throws 
InvalidFormatParametersException
-             {
-        if (params.length() != 0) {
-            throw new InvalidFormatParametersException(
-                    "This number format doesn't support any parameters.");
-        }
-    }
-
-    /**
-     * Utility method to extract the {@link Number} from an {@link 
TemplateNumberModel}, and throws
-     * {@link TemplateModelException} with a standard error message if that's 
{@code null}. {@link TemplateNumberModel}
-     * that store {@code null} are in principle not allowed, and so are 
considered to be bugs in the
-     * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation.
-     */
-    public static Number getNonNullNumber(TemplateNumberModel numberModel)
-            throws TemplateModelException, UnformattableValueException {
-        Number number = numberModel.getAsNumber();
-        if (number == null) {
-            throw EvalUtil.newModelHasStoredNullException(Number.class, 
numberModel, null);
-        }
-        return number;
-    }
-
-    /**
-     * Utility method to extract the {@link Date} from an {@link 
TemplateDateModel}, and throw
-     * {@link TemplateModelException} with a standard error message if that's 
{@code null}. {@link TemplateDateModel}
-     * that store {@code null} are in principle not allowed, and so are 
considered to be bugs in the
-     * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation.
-     */
-    public static Date getNonNullDate(TemplateDateModel dateModel) throws 
TemplateModelException {
-        Date date = dateModel.getAsDate();
-        if (date == null) {
-            throw EvalUtil.newModelHasStoredNullException(Date.class, 
dateModel, null);
-        }
-        return date;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java 
b/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java
deleted file mode 100644
index cc35724..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateNumberFormat.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.text.NumberFormat;
-
-import org.apache.freemarker.core.model.TemplateDateModel;
-import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
-import org.apache.freemarker.core.model.TemplateModelException;
-import org.apache.freemarker.core.model.TemplateNumberModel;
-
-/**
- * Represents a number format; used in templates for formatting and parsing 
with that format. This is similar to Java's
- * {@link NumberFormat}, but made to fit the requirements of FreeMarker. Also, 
it makes easier to define formats that
- * can't be represented with Java's existing {@link NumberFormat} 
implementations.
- * 
- * <p>
- * Implementations need not be thread-safe if the {@link 
TemplateNumberFormatFactory} 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.
- * 
- * @since 2.3.24
- */
-public abstract class TemplateNumberFormat extends TemplateValueFormat {
-
-    /**
-     * @param numberModel
-     *            The number to format; not {@code null}. 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 number as text, with no escaping (like no HTML escaping); 
can't be {@code null}.
-     * 
-     * @throws TemplateValueFormatException
-     *             If any problem occurs while parsing/getting the format. 
Notable subclass:
-     *             {@link UnformattableValueException}.
-     * @throws TemplateModelException
-     *             Exception thrown by the {@code dateModel} object when 
calling its methods.
-     */
-    public abstract String formatToPlainText(TemplateNumberModel numberModel)
-            throws TemplateValueFormatException, TemplateModelException;
-
-    /**
-     * Formats the model to markup instead of to plain text if the result 
markup will be more than just plain text
-     * escaped, otherwise falls back to formatting to plain text. If the 
markup result would be just the result of
-     * {@link #formatToPlainText(TemplateNumberModel)} escaped, it must return 
the {@link String} that
-     * {@link #formatToPlainText(TemplateNumberModel)} does.
-     * 
-     * <p>
-     * The implementation in {@link TemplateNumberFormat} simply calls {@link 
#formatToPlainText(TemplateNumberModel)}.
-     * 
-     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not 
{@code null}.
-     */
-    public Object format(TemplateNumberModel numberModel)
-            throws TemplateValueFormatException, TemplateModelException {
-        return formatToPlainText(numberModel);
-    }
-    
-    /**
-     * Tells if this formatter should be re-created if the locale changes.
-     */
-    public abstract boolean isLocaleBound();
-
-    /**
-     * This method is reserved for future purposes; currently it always throws 
{@link ParsingNotSupportedException}. We
-     * don't yet support number parsing with {@link TemplateNumberFormat}-s, 
because currently FTL parses strings to
-     * number with the {@link ArithmeticEngine} ({@link TemplateNumberFormat} 
were only introduced in 2.3.24). If it
-     * will be support, it will be similar to {@link 
TemplateDateFormat#parse(String, int)}.
-     */
-    public final Object parse(String s) throws TemplateValueFormatException {
-        throw new ParsingNotSupportedException("Number formats currenly don't 
support parsing");
-    }
-    
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java 
b/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java
deleted file mode 100644
index 4decb91..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateNumberFormatFactory.java
+++ /dev/null
@@ -1,62 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.util.Locale;
-
-/**
- * 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.
- * 
- * @see Configurable#setCustomNumberFormats(java.util.Map)
- * 
- * @since 2.3.24
- */
-public abstract class TemplateNumberFormatFactory extends 
TemplateValueFormatFactory {
-
-    /**
-     * Returns a formatter for the given parameters.
-     * 
-     * <p>
-     * The returned formatter can be a new instance or a reused (cached) 
instance. Note that {@link Environment} itself
-     * caches the returned instances, though that cache is lost with the 
{@link Environment} (i.e., when the top-level
-     * template execution ends), also it might flushes lot of entries if the 
locale or time zone is changed during
-     * template execution. So caching on the factory level is still useful, 
unless creating the formatters is
-     * sufficiently cheap.
-     * 
-     * @param params
-     *            The string that further describes how the format should 
look. For example, when the
-     *            {@link Configurable#getNumberFormat() numberFormat} 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 TemplateNumberFormatFactory} implementation. Not 
{@code null}, often an empty string.
-     * @param locale
-     *            The locale to format for. Not {@code null}. The resulting 
format must be bound to this locale
-     *            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 TemplateValueFormatException;
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/TemplateValueFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/TemplateValueFormat.java 
b/src/main/java/org/apache/freemarker/core/TemplateValueFormat.java
deleted file mode 100644
index cc2833c..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateValueFormat.java
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * 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 org.apache.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/ef968757/src/main/java/org/apache/freemarker/core/TemplateValueFormatException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateValueFormatException.java 
b/src/main/java/org/apache/freemarker/core/TemplateValueFormatException.java
deleted file mode 100644
index f952369..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateValueFormatException.java
+++ /dev/null
@@ -1,37 +0,0 @@
-/*
- * 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 org.apache.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/ef968757/src/main/java/org/apache/freemarker/core/TemplateValueFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/TemplateValueFormatFactory.java 
b/src/main/java/org/apache/freemarker/core/TemplateValueFormatFactory.java
deleted file mode 100644
index 3c504de..0000000
--- a/src/main/java/org/apache/freemarker/core/TemplateValueFormatFactory.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * 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 org.apache.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/ef968757/src/main/java/org/apache/freemarker/core/UndefinedCustomFormatException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/UndefinedCustomFormatException.java 
b/src/main/java/org/apache/freemarker/core/UndefinedCustomFormatException.java
deleted file mode 100644
index 26561f3..0000000
--- 
a/src/main/java/org/apache/freemarker/core/UndefinedCustomFormatException.java
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-/**
- * @since 2.3.24
- */
-public class UndefinedCustomFormatException extends 
InvalidFormatStringException {
-
-    public UndefinedCustomFormatException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public UndefinedCustomFormatException(String message) {
-        super(message);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/UnformattableValueException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/UnformattableValueException.java 
b/src/main/java/org/apache/freemarker/core/UnformattableValueException.java
deleted file mode 100644
index cb30c92..0000000
--- a/src/main/java/org/apache/freemarker/core/UnformattableValueException.java
+++ /dev/null
@@ -1,41 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import org.apache.freemarker.core.model.TemplateModel;
-
-/**
- * Thrown when a {@link TemplateModel} can't be formatted because of the 
value/properties of it are outside of that the
- * {@link TemplateValueFormat} supports. For example, a formatter may not 
support dates before year 1, or can't format
- * NaN. The most often used subclass is {@link 
UnknownDateTypeFormattingUnsupportedException}.
- * 
- * @since 2.3.24
- */
-public class UnformattableValueException extends TemplateValueFormatException {
-
-    public UnformattableValueException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public UnformattableValueException(String message) {
-        super(message);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
 
b/src/main/java/org/apache/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
deleted file mode 100644
index aceca07..0000000
--- 
a/src/main/java/org/apache/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import org.apache.freemarker.core.model.TemplateDateModel;
-
-/**
- * Thrown when a {@link TemplateDateModel} can't be formatted because its type 
is {@link TemplateDateModel#UNKNOWN}.
- * 
- * @since 2.3.24
- */
-public final class UnknownDateTypeFormattingUnsupportedException extends 
UnformattableValueException {
-
-    public UnknownDateTypeFormattingUnsupportedException() {
-        super(MessageUtil.UNKNOWN_DATE_TO_STRING_ERROR_MESSAGE);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/UnknownDateTypeParsingUnsupportedException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/UnknownDateTypeParsingUnsupportedException.java
 
b/src/main/java/org/apache/freemarker/core/UnknownDateTypeParsingUnsupportedException.java
deleted file mode 100644
index a1173b6..0000000
--- 
a/src/main/java/org/apache/freemarker/core/UnknownDateTypeParsingUnsupportedException.java
+++ /dev/null
@@ -1,36 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import org.apache.freemarker.core.model.TemplateDateModel;
-
-/**
- * Thrown when a string can't be parsed to {@link TemplateDateModel}, because 
the provided target type is
- * {@link TemplateDateModel#UNKNOWN}.
- * 
- * @since 2.3.24
- */
-public final class UnknownDateTypeParsingUnsupportedException extends 
UnformattableValueException {
-
-    public UnknownDateTypeParsingUnsupportedException() {
-        super(MessageUtil.UNKNOWN_DATE_PARSING_ERROR_MESSAGE);
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/UnparsableValueException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/UnparsableValueException.java 
b/src/main/java/org/apache/freemarker/core/UnparsableValueException.java
deleted file mode 100644
index 37d979d..0000000
--- a/src/main/java/org/apache/freemarker/core/UnparsableValueException.java
+++ /dev/null
@@ -1,38 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-/**
- * Thrown when the content of the string that should be parsed by the {@link 
TemplateValueFormat} doesn't match what the
- * format expects.
- * 
- * @since 2.3.24
- */
-public class UnparsableValueException extends TemplateValueFormatException {
-
-    public UnparsableValueException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public UnparsableValueException(String message) {
-        this(message, null);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/XSTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/XSTemplateDateFormat.java 
b/src/main/java/org/apache/freemarker/core/XSTemplateDateFormat.java
deleted file mode 100644
index 56135f3..0000000
--- a/src/main/java/org/apache/freemarker/core/XSTemplateDateFormat.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.util.Date;
-import java.util.TimeZone;
-
-import org.apache.freemarker.core.util._DateUtil;
-import org.apache.freemarker.core.util._DateUtil.CalendarFieldsToDateConverter;
-import org.apache.freemarker.core.util._DateUtil.DateParseException;
-import org.apache.freemarker.core.util._DateUtil.DateToISO8601CalendarFactory;
-
-/**
- * XML Schema format.
- */
-final class XSTemplateDateFormat extends ISOLikeTemplateDateFormat {
-
-    XSTemplateDateFormat(
-            String settingValue, int parsingStart,
-            int dateType,
-            boolean zonelessInput,
-            TimeZone timeZone,
-            ISOLikeTemplateDateFormatFactory factory,
-            Environment env)
-            throws UnknownDateTypeFormattingUnsupportedException, 
InvalidFormatParametersException {
-        super(settingValue, parsingStart, dateType, zonelessInput, timeZone, 
factory, env);
-    }
-    
-    @Override
-    protected String format(Date date, boolean datePart, boolean timePart, 
boolean offsetPart, int accuracy,
-            TimeZone timeZone, DateToISO8601CalendarFactory calendarFactory) {
-        return _DateUtil.dateToXSString(
-                date, datePart, timePart, offsetPart, accuracy, timeZone, 
calendarFactory);
-    }
-
-    @Override
-    protected Date parseDate(String s, TimeZone tz, 
CalendarFieldsToDateConverter calToDateConverter)
-            throws DateParseException {
-        return _DateUtil.parseXSDate(s, tz, calToDateConverter);
-    }
-
-    @Override
-    protected Date parseTime(String s, TimeZone tz, 
CalendarFieldsToDateConverter calToDateConverter)
-            throws DateParseException {
-        return _DateUtil.parseXSTime(s, tz, calToDateConverter);
-    }
-
-    @Override
-    protected Date parseDateTime(String s, TimeZone tz,
-            CalendarFieldsToDateConverter calToDateConverter) throws 
DateParseException {
-        return _DateUtil.parseXSDateTime(s, tz, calToDateConverter);
-    }
-
-    @Override
-    protected String getDateDescription() {
-        return "W3C XML Schema date";
-    }
-
-    @Override
-    protected String getTimeDescription() {
-        return "W3C XML Schema time";
-    }
-
-    @Override
-    protected String getDateTimeDescription() {
-        return "W3C XML Schema dateTime";
-    }
-
-    @Override
-    protected boolean isXSMode() {
-        return true;
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/XSTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/XSTemplateDateFormatFactory.java 
b/src/main/java/org/apache/freemarker/core/XSTemplateDateFormatFactory.java
deleted file mode 100644
index 9421cc0..0000000
--- a/src/main/java/org/apache/freemarker/core/XSTemplateDateFormatFactory.java
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * 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 org.apache.freemarker.core;
-
-import java.util.Locale;
-import java.util.TimeZone;
-
-class XSTemplateDateFormatFactory extends ISOLikeTemplateDateFormatFactory {
-    
-    static final XSTemplateDateFormatFactory INSTANCE = new 
XSTemplateDateFormatFactory();
-
-    private XSTemplateDateFormatFactory() {
-        // Not meant to be instantiated
-    }
-
-    @Override
-    public TemplateDateFormat get(String params, int dateType, Locale locale, 
TimeZone timeZone, boolean zonelessInput,
-            Environment env) throws 
UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException 
{
-        // We don't cache these as creating them is cheap (only 10% speedup of 
${d?string.xs} with caching)
-        return new XSTemplateDateFormat(
-                params, 2,
-                dateType, zonelessInput,
-                timeZone, this, env);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/_EvalUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/org/apache/freemarker/core/_EvalUtil.java 
b/src/main/java/org/apache/freemarker/core/_EvalUtil.java
new file mode 100644
index 0000000..fb0e7a5
--- /dev/null
+++ b/src/main/java/org/apache/freemarker/core/_EvalUtil.java
@@ -0,0 +1,543 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import java.util.Date;
+
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateScalarModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateValueFormat;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+
+/**
+ * Internally used static utilities for evaluation expressions.
+ */
+public class _EvalUtil {
+    static final int CMP_OP_EQUALS = 1;
+    static final int CMP_OP_NOT_EQUALS = 2;
+    static final int CMP_OP_LESS_THAN = 3;
+    static final int CMP_OP_GREATER_THAN = 4;
+    static final int CMP_OP_LESS_THAN_EQUALS = 5;
+    static final int CMP_OP_GREATER_THAN_EQUALS = 6;
+    // If you add a new operator here, update the "compare" and 
"cmpOpToString" methods!
+    
+    // Prevents instantination.
+    private _EvalUtil() { }
+    
+    /**
+     * @param expr {@code null} is allowed, but may results in less helpful 
error messages
+     * @param env {@code null} is allowed
+     */
+    static String modelToString(TemplateScalarModel model, ASTExpression expr, 
Environment env)
+    throws TemplateModelException {
+        String value = model.getAsString();
+        if (value == null) {
+            throw newModelHasStoredNullException(String.class, model, expr);
+        }
+        return value;
+    }
+    
+    /**
+     * @param expr {@code null} is allowed, but may results in less helpful 
error messages
+     */
+    static Number modelToNumber(TemplateNumberModel model, ASTExpression expr)
+        throws TemplateModelException {
+        Number value = model.getAsNumber();
+        if (value == null) throw newModelHasStoredNullException(Number.class, 
model, expr);
+        return value;
+    }
+
+    /**
+     * @param expr {@code null} is allowed, but may results in less helpful 
error messages
+     */
+    static Date modelToDate(TemplateDateModel model, ASTExpression expr)
+        throws TemplateModelException {
+        Date value = model.getAsDate();
+        if (value == null) throw newModelHasStoredNullException(Date.class, 
model, expr);
+        return value;
+    }
+    
+    /** Signals the buggy case where we have a non-null model, but it wraps a 
null. */
+    public static TemplateModelException newModelHasStoredNullException(
+            Class expected, TemplateModel model, ASTExpression expr) {
+        return new _TemplateModelException(expr,
+                
_TemplateModelException.modelHasStoredNullDescription(expected, model));
+    }
+
+    /**
+     * Compares two expressions according the rules of the FTL comparator 
operators.
+     * 
+     * @param leftExp not {@code null}
+     * @param operator one of the {@code COMP_OP_...} constants, like {@link 
#CMP_OP_EQUALS}.
+     * @param operatorString can be null {@code null}; the actual operator 
used, used for more accurate error message.
+     * @param rightExp not {@code null}
+     * @param env {@code null} is tolerated, but should be avoided
+     */
+    static boolean compare(
+            ASTExpression leftExp,
+            int operator, String  operatorString,
+            ASTExpression rightExp,
+            ASTExpression defaultBlamed,
+            Environment env) throws TemplateException {
+        TemplateModel ltm = leftExp.eval(env);
+        TemplateModel rtm = rightExp.eval(env);
+        return compare(
+                ltm, leftExp,
+                operator, operatorString,
+                rtm, rightExp,
+                defaultBlamed, false,
+                false, false, false,
+                env);
+    }
+    
+    /**
+     * Compares values according the rules of the FTL comparator operators; if 
the {@link ASTExpression}-s are
+     * accessible, use {@link #compare(ASTExpression, int, String, 
ASTExpression, ASTExpression, Environment)} instead,
+     * as that gives better error messages.
+     * 
+     * @param leftValue maybe {@code null}, which will usually cause the 
appropriate {@link TemplateException}. 
+     * @param operator one of the {@code COMP_OP_...} constants, like {@link 
#CMP_OP_EQUALS}.
+     * @param rightValue maybe {@code null}, which will usually cause the 
appropriate {@link TemplateException}.
+     * @param env {@code null} is tolerated, but should be avoided
+     */
+    static boolean compare(
+            TemplateModel leftValue, int operator, TemplateModel rightValue,
+            Environment env) throws TemplateException {
+        return compare(
+                leftValue, null,
+                operator, null,
+                rightValue, null,
+                null, false,
+                false, false, false,
+                env);
+    }
+
+    /**
+     * Same as {@link #compare(TemplateModel, int, TemplateModel, 
Environment)}, but if the two types are incompatible,
+     *     they are treated as non-equal instead of throwing an exception. 
Comparing dates of different types will
+     *     still throw an exception, however.
+     */
+    static boolean compareLenient(
+            TemplateModel leftValue, int operator, TemplateModel rightValue,
+            Environment env) throws TemplateException {
+        return compare(
+                leftValue, null,
+                operator, null,
+                rightValue, null,
+                null, false,
+                true, false, false,
+                env);
+    }
+    
+    private static final String VALUE_OF_THE_COMPARISON_IS_UNKNOWN_DATE_LIKE
+            = "value of the comparison is a date-like value where "
+              + "it's not known if it's a date (no time part), time, or 
date-time, "
+              + "and thus can't be used in a comparison.";
+    
+    /**
+     * @param leftExp {@code null} is allowed, but may results in less helpful 
error messages
+     * @param operator one of the {@code COMP_OP_...} constants, like {@link 
#CMP_OP_EQUALS}.
+     * @param operatorString can be null {@code null}; the actual operator 
used, used for more accurate error message.
+     * @param rightExp {@code null} is allowed, but may results in less 
helpful error messages
+     * @param defaultBlamed {@code null} allowed; the expression to which the 
error will point to if something goes
+     *        wrong that is not specific to the left or right side expression, 
or if that expression is {@code null}.
+     * @param typeMismatchMeansNotEqual If the two types are incompatible, 
they are treated as non-equal instead
+     *     of throwing an exception. Comparing dates of different types will 
still throw an exception, however. 
+     * @param leftNullReturnsFalse if {@code true}, a {@code null} left value 
will not cause exception, but make the
+     *     expression {@code false}.  
+     * @param rightNullReturnsFalse if {@code true}, a {@code null} right 
value will not cause exception, but make the
+     *     expression {@code false}.  
+     */
+    static boolean compare(
+            TemplateModel leftValue, ASTExpression leftExp,
+            int operator, String operatorString,
+            TemplateModel rightValue, ASTExpression rightExp,
+            ASTExpression defaultBlamed, boolean quoteOperandsInErrors,
+            boolean typeMismatchMeansNotEqual,
+            boolean leftNullReturnsFalse, boolean rightNullReturnsFalse,
+            Environment env) throws TemplateException {
+        if (leftValue == null) {
+            if (leftNullReturnsFalse) { 
+                return false;
+            } else {
+                if (leftExp != null) {
+                    throw InvalidReferenceException.getInstance(leftExp, env);
+                } else {
+                    throw new _MiscTemplateException(defaultBlamed, env, 
+                                "The left operand of the comparison was 
undefined or null.");
+                }
+            }
+        }
+
+        if (rightValue == null) {
+            if (rightNullReturnsFalse) { 
+                return false;
+            } else {
+                if (rightExp != null) {
+                    throw InvalidReferenceException.getInstance(rightExp, env);
+                } else {
+                    throw new _MiscTemplateException(defaultBlamed, env,
+                                "The right operand of the comparison was 
undefined or null.");
+                }
+            }
+        }
+
+        final int cmpResult;
+        if (leftValue instanceof TemplateNumberModel && rightValue instanceof 
TemplateNumberModel) {
+            Number leftNum = _EvalUtil.modelToNumber((TemplateNumberModel) 
leftValue, leftExp);
+            Number rightNum = _EvalUtil.modelToNumber((TemplateNumberModel) 
rightValue, rightExp);
+            ArithmeticEngine ae =
+                    env != null
+                        ? env.getArithmeticEngine()
+                        : (leftExp != null
+                            ? leftExp.getTemplate().getArithmeticEngine()
+                            : ArithmeticEngine.BIGDECIMAL_ENGINE);
+            try {
+                cmpResult = ae.compareNumbers(leftNum, rightNum);
+            } catch (RuntimeException e) {
+                throw new _MiscTemplateException(defaultBlamed, e, env,
+                        "Unexpected error while comparing two numbers: ", e);
+            }
+        } else if (leftValue instanceof TemplateDateModel && rightValue 
instanceof TemplateDateModel) {
+            TemplateDateModel leftDateModel = (TemplateDateModel) leftValue;
+            TemplateDateModel rightDateModel = (TemplateDateModel) rightValue;
+            
+            int leftDateType = leftDateModel.getDateType();
+            int rightDateType = rightDateModel.getDateType();
+            
+            if (leftDateType == TemplateDateModel.UNKNOWN || rightDateType == 
TemplateDateModel.UNKNOWN) {
+                String sideName;
+                ASTExpression sideExp;
+                if (leftDateType == TemplateDateModel.UNKNOWN) {
+                    sideName = "left";
+                    sideExp = leftExp;
+                } else {
+                    sideName = "right";
+                    sideExp = rightExp;
+                }
+                
+                throw new _MiscTemplateException(sideExp != null ? sideExp : 
defaultBlamed, env,
+                        "The ", sideName, " ", 
VALUE_OF_THE_COMPARISON_IS_UNKNOWN_DATE_LIKE);
+            }
+            
+            if (leftDateType != rightDateType) {
+                throw new _MiscTemplateException(defaultBlamed, env,
+                        "Can't compare dates of different types. Left date 
type is ",
+                        TemplateDateModel.TYPE_NAMES.get(leftDateType), ", 
right date type is ",
+                        TemplateDateModel.TYPE_NAMES.get(rightDateType), ".");
+            }
+
+            Date leftDate = _EvalUtil.modelToDate(leftDateModel, leftExp);
+            Date rightDate = _EvalUtil.modelToDate(rightDateModel, rightExp);
+            cmpResult = leftDate.compareTo(rightDate);
+        } else if (leftValue instanceof TemplateScalarModel && rightValue 
instanceof TemplateScalarModel) {
+            if (operator != CMP_OP_EQUALS && operator != CMP_OP_NOT_EQUALS) {
+                throw new _MiscTemplateException(defaultBlamed, env,
+                        "Can't use operator \"", cmpOpToString(operator, 
operatorString), "\" on string values.");
+            }
+            String leftString = _EvalUtil.modelToString((TemplateScalarModel) 
leftValue, leftExp, env);
+            String rightString = _EvalUtil.modelToString((TemplateScalarModel) 
rightValue, rightExp, env);
+            // FIXME NBC: Don't use the Collator here. That's locale-specific, 
but ==/!= should not be.
+            cmpResult = env.getCollator().compare(leftString, rightString);
+        } else if (leftValue instanceof TemplateBooleanModel && rightValue 
instanceof TemplateBooleanModel) {
+            if (operator != CMP_OP_EQUALS && operator != CMP_OP_NOT_EQUALS) {
+                throw new _MiscTemplateException(defaultBlamed, env,
+                        "Can't use operator \"", cmpOpToString(operator, 
operatorString), "\" on boolean values.");
+            }
+            boolean leftBool = ((TemplateBooleanModel) 
leftValue).getAsBoolean();
+            boolean rightBool = ((TemplateBooleanModel) 
rightValue).getAsBoolean();
+            cmpResult = (leftBool ? 1 : 0) - (rightBool ? 1 : 0);
+        } else {
+            if (typeMismatchMeansNotEqual) {
+                if (operator == CMP_OP_EQUALS) {
+                    return false;
+                } else if (operator == CMP_OP_NOT_EQUALS) {
+                    return true;
+                }
+                // Falls through
+            }
+            throw new _MiscTemplateException(defaultBlamed, env,
+                    "Can't compare values of these types. ",
+                    "Allowed comparisons are between two numbers, two strings, 
two dates, or two booleans.\n",
+                    "Left hand operand ",
+                    (quoteOperandsInErrors && leftExp != null
+                            ? new Object[] { "(", new 
_DelayedGetCanonicalForm(leftExp), ") value " }
+                            : ""),
+                    "is ", new _DelayedAOrAn(new 
_DelayedFTLTypeDescription(leftValue)), ".\n",
+                    "Right hand operand ",
+                    (quoteOperandsInErrors && rightExp != null
+                            ? new Object[] { "(", new 
_DelayedGetCanonicalForm(rightExp), ") value " }
+                            : ""),
+                    "is ", new _DelayedAOrAn(new 
_DelayedFTLTypeDescription(rightValue)),
+                    ".");
+        }
+
+        switch (operator) {
+            case CMP_OP_EQUALS: return cmpResult == 0;
+            case CMP_OP_NOT_EQUALS: return cmpResult != 0;
+            case CMP_OP_LESS_THAN: return cmpResult < 0;
+            case CMP_OP_GREATER_THAN: return cmpResult > 0;
+            case CMP_OP_LESS_THAN_EQUALS: return cmpResult <= 0;
+            case CMP_OP_GREATER_THAN_EQUALS: return cmpResult >= 0;
+            default: throw new BugException("Unsupported comparator operator 
code: " + operator);
+        }
+    }
+
+    private static String cmpOpToString(int operator, String operatorString) {
+        if (operatorString != null) {
+            return operatorString;
+        } else {
+            switch (operator) {
+                case CMP_OP_EQUALS: return "equals";
+                case CMP_OP_NOT_EQUALS: return "not-equals";
+                case CMP_OP_LESS_THAN: return "less-than";
+                case CMP_OP_GREATER_THAN: return "greater-than";
+                case CMP_OP_LESS_THAN_EQUALS: return "less-than-equals";
+                case CMP_OP_GREATER_THAN_EQUALS: return "greater-than-equals";
+                default: return "???";
+            }
+        }
+    }
+
+    /**
+     * Converts a value to plain text {@link String}, or a {@link 
TemplateMarkupOutputModel} if that's what the
+     * {@link TemplateValueFormat} involved produces.
+     * 
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's 
sequence or collection.
+     * 
+     * @return Never {@code null}
+     */
+    static Object coerceModelToStringOrMarkup(TemplateModel tm, ASTExpression 
exp, String seqTip, Environment env)
+            throws TemplateException {
+        return coerceModelToStringOrMarkup(tm, exp, false, seqTip, env);
+    }
+    
+    /**
+     * @return {@code null} if the {@code returnNullOnNonCoercableType} 
parameter is {@code true}, and the coercion is
+     *         not possible, because of the type is not right for it.
+     * 
+     * @see #coerceModelToStringOrMarkup(TemplateModel, ASTExpression, String, 
Environment)
+     */
+    static Object coerceModelToStringOrMarkup(
+            TemplateModel tm, ASTExpression exp, boolean 
returnNullOnNonCoercableType, String seqTip, Environment env)
+            throws TemplateException {
+        if (tm instanceof TemplateNumberModel) {
+            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
+            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, 
false);
+            try {
+                return assertFormatResultNotNull(format.format(tnm));
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatNumberException(format, exp, e, 
false);
+            }
+        } else if (tm instanceof TemplateDateModel) {
+            TemplateDateModel tdm = (TemplateDateModel) tm;
+            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, 
false);
+            try {
+                return assertFormatResultNotNull(format.format(tdm));
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatDateException(format, exp, e, 
false);
+            }
+        } else if (tm instanceof TemplateMarkupOutputModel) {
+            return tm;
+        } else { 
+            return coerceModelToTextualCommon(tm, exp, seqTip, true, 
returnNullOnNonCoercableType, env);
+        }
+    }
+
+    /**
+     * Like {@link #coerceModelToStringOrMarkup(TemplateModel, ASTExpression, 
String, Environment)}, but gives error
+     * if the result is markup. This is what you normally use where markup 
results can't be used.
+     *
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's 
sequence or collection.
+     * 
+     * @return Never {@code null}
+     */
+    static String coerceModelToStringOrUnsupportedMarkup(
+            TemplateModel tm, ASTExpression exp, String seqTip, Environment 
env)
+            throws TemplateException {
+        if (tm instanceof TemplateNumberModel) {
+            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
+            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, 
false);
+            try {
+                return ensureFormatResultString(format.format(tnm), exp, env);
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatNumberException(format, exp, e, 
false);
+            }
+        } else if (tm instanceof TemplateDateModel) {
+            TemplateDateModel tdm = (TemplateDateModel) tm;
+            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, 
false);
+            try {
+                return ensureFormatResultString(format.format(tdm), exp, env);
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatDateException(format, exp, e, 
false);
+            }
+        } else { 
+            return coerceModelToTextualCommon(tm, exp, seqTip, false, false, 
env);
+        }
+    }
+
+    /**
+     * Converts a value to plain text {@link String}, even if the {@link 
TemplateValueFormat} involved normally produces
+     * markup. This should be used rarely, where the user clearly intend to 
use the plain text variant of the format.
+     * 
+     * @param seqTip
+     *            Tip to display if the value type is not coercable, but it's 
sequence or collection.
+     * 
+     * @return Never {@code null}
+     */
+    static String coerceModelToPlainText(TemplateModel tm, ASTExpression exp, 
String seqTip,
+            Environment env) throws TemplateException {
+        if (tm instanceof TemplateNumberModel) {
+            return 
assertFormatResultNotNull(env.formatNumberToPlainText((TemplateNumberModel) tm, 
exp, false));
+        } else if (tm instanceof TemplateDateModel) {
+            return 
assertFormatResultNotNull(env.formatDateToPlainText((TemplateDateModel) tm, 
exp, false));
+        } else {
+            return coerceModelToTextualCommon(tm, exp, seqTip, false, false, 
env);
+        }
+    }
+
+    /**
+     * @param tm
+     *            If {@code null} that's an exception
+     * 
+     * @param supportsTOM
+     *            Whether the caller {@code coerceModelTo...} method could 
handle a {@link TemplateMarkupOutputModel}.
+     *            
+     * @return Never {@code null}
+     */
+    private static String coerceModelToTextualCommon(
+            TemplateModel tm, ASTExpression exp, String seqHint, boolean 
supportsTOM, boolean returnNullOnNonCoercableType,
+            Environment env)
+            throws TemplateException {
+        if (tm instanceof TemplateScalarModel) {
+            return modelToString((TemplateScalarModel) tm, exp, env);
+        } else if (tm == null) {
+            if (exp != null) {
+                throw InvalidReferenceException.getInstance(exp, env);
+            } else {
+                throw new InvalidReferenceException(
+                        "Null/missing value (no more informatoin avilable)",
+                        env);
+            }
+        } else if (tm instanceof TemplateBooleanModel) {
+            // [FM3] This should be before TemplateScalarModel, but automatic 
boolean-to-string is only non-error since
+            // 2.3.20, so to keep backward compatibility we couldn't insert 
this before TemplateScalarModel.
+            boolean booleanValue = ((TemplateBooleanModel) tm).getAsBoolean();
+            return env.formatBoolean(booleanValue, false);
+        } else {
+            if (returnNullOnNonCoercableType) {
+                return null;
+            }
+            if (seqHint != null && (tm instanceof TemplateSequenceModel || tm 
instanceof TemplateCollectionModel)) {
+                if (supportsTOM) {
+                    throw new NonStringOrTemplateOutputException(exp, tm, 
seqHint, env);
+                } else {
+                    throw new NonStringException(exp, tm, seqHint, env);
+                }
+            } else {
+                if (supportsTOM) {
+                    throw new NonStringOrTemplateOutputException(exp, tm, env);
+                } else {
+                    throw new NonStringException(exp, tm, env);
+                }
+            }
+        }
+    }
+
+    private static String ensureFormatResultString(Object formatResult, 
ASTExpression exp, Environment env)
+            throws NonStringException {
+        if (formatResult instanceof String) { 
+            return (String) formatResult;
+        }
+        
+        assertFormatResultNotNull(formatResult);
+        
+        TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) 
formatResult;
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Value was formatted to convert it to string, but the result 
was markup of ouput format ",
+                new _DelayedJQuote(mo.getOutputFormat()), ".")
+                .tip("Use value?string to force formatting to plain text.")
+                .blame(exp);
+        throw new NonStringException(null, desc);
+    }
+
+    static String assertFormatResultNotNull(String r) {
+        if (r != null) {
+            return r;
+        }
+        throw new NullPointerException("TemplateValueFormatter result can't be 
null");
+    }
+
+    static Object assertFormatResultNotNull(Object r) {
+        if (r != null) {
+            return r;
+        }
+        throw new NullPointerException("TemplateValueFormatter result can't be 
null");
+    }
+
+    static TemplateMarkupOutputModel concatMarkupOutputs(ASTNode parent, 
TemplateMarkupOutputModel leftMO,
+            TemplateMarkupOutputModel rightMO) throws TemplateException {
+        MarkupOutputFormat leftOF = leftMO.getOutputFormat();
+        MarkupOutputFormat rightOF = rightMO.getOutputFormat();
+        if (rightOF != leftOF) {
+            String rightPT;
+            String leftPT;
+            if ((rightPT = rightOF.getSourcePlainText(rightMO)) != null) {
+                return leftOF.concat(leftMO, 
leftOF.fromPlainTextByEscaping(rightPT));
+            } else if ((leftPT = leftOF.getSourcePlainText(leftMO)) != null) {
+                return rightOF.concat(rightOF.fromPlainTextByEscaping(leftPT), 
rightMO);
+            } else {
+                Object[] message = { "Concatenation left hand operand is in ", 
new _DelayedToString(leftOF),
+                        " format, while the right hand operand is in ", new 
_DelayedToString(rightOF),
+                        ". Conversion to common format wasn't possible." };
+                if (parent instanceof ASTExpression) {
+                    throw new _MiscTemplateException((ASTExpression) parent, 
message);
+                } else {
+                    throw new _MiscTemplateException(message);
+                }
+            }
+        } else {
+            return leftOF.concat(leftMO, rightMO);
+        }
+    }
+
+    /**
+     * Returns an {@link ArithmeticEngine} even if {@code env} is {@code 
null}, because we are in parsing phase.
+     */
+    static ArithmeticEngine getArithmeticEngine(Environment env, ASTNode tObj) 
{
+        return env != null
+                ? env.getArithmeticEngine()
+                : 
tObj.getTemplate().getParserConfiguration().getArithmeticEngine();
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ef968757/src/main/java/org/apache/freemarker/core/valueformat/InvalidFormatParametersException.java
----------------------------------------------------------------------
diff --git 
a/src/main/java/org/apache/freemarker/core/valueformat/InvalidFormatParametersException.java
 
b/src/main/java/org/apache/freemarker/core/valueformat/InvalidFormatParametersException.java
new file mode 100644
index 0000000..d7502a4
--- /dev/null
+++ 
b/src/main/java/org/apache/freemarker/core/valueformat/InvalidFormatParametersException.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 org.apache.freemarker.core.valueformat;
+
+/**
+ * Used when creating {@link TemplateDateFormat}-s and {@link 
TemplateNumberFormat}-s to indicate that the parameters
+ * part of the format string (like some kind of pattern) is malformed.
+ * 
+ * @since 2.3.24
+ */
+public final class InvalidFormatParametersException extends 
InvalidFormatStringException {
+    
+    public InvalidFormatParametersException(String message, Throwable cause) {
+        super(message, cause);
+    }
+
+    public InvalidFormatParametersException(String message) {
+        this(message, null);
+    }
+
+}


Reply via email to