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/24 22:21:34 UTC

[01/12] incubator-freemarker git commit: Unifying UnformattableDateException and UnformattableNumberException as UnformattableValueException. Added UnparsableValueException, using it for TemplateDateFormat.parse.

Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3 53bf6b3b3 -> a0eaf3da9


Unifying UnformattableDateException and UnformattableNumberException as UnformattableValueException. Added UnparsableValueException, using it for TemplateDateFormat.parse.


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

Branch: refs/heads/2.3
Commit: 03e8fc070cec335ecfe3c11aaf5c1979b858ca3e
Parents: 89ec237
Author: ddekany <dd...@apache.org>
Authored: Thu Sep 17 12:15:07 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Sep 17 12:15:07 2015 +0200

----------------------------------------------------------------------
 .../BackwardCompatibleTemplateNumberFormat.java |  2 +-
 .../core/BuiltInsForMultipleTypes.java          |  2 +-
 src/main/java/freemarker/core/Environment.java  |  2 +-
 .../core/ISOLikeTemplateDateFormat.java         | 27 ++++++++-----
 .../freemarker/core/JavaTemplateDateFormat.java | 10 +++--
 .../core/JavaTemplateNumberFormat.java          |  8 ++--
 .../freemarker/core/TemplateDateFormat.java     |  2 +-
 .../freemarker/core/TemplateFormatUtil.java     |  6 +--
 .../freemarker/core/TemplateNumberFormat.java   |  2 +-
 .../core/UnformattableDateException.java        | 41 --------------------
 .../core/UnformattableNumberException.java      | 41 --------------------
 .../core/UnformattableValueException.java       | 41 ++++++++++++++++++++
 ...nDateTypeFormattingUnsupportedException.java |  2 +-
 .../core/UnparsableValueException.java          | 38 ++++++++++++++++++
 .../core/BaseNTemplateNumberFormatFactory.java  |  4 +-
 ...EpochMillisDivTemplateDateFormatFactory.java |  9 ++---
 .../EpochMillisTemplateDateFormatFactory.java   |  9 ++---
 .../core/HexTemplateNumberFormatFactory.java    |  6 +--
 ...AndTZSensitiveTemplateDateFormatFactory.java | 11 +++---
 ...aleSensitiveTemplateNumberFormatFactory.java |  6 +--
 20 files changed, 137 insertions(+), 132 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/main/java/freemarker/core/BackwardCompatibleTemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BackwardCompatibleTemplateNumberFormat.java b/src/main/java/freemarker/core/BackwardCompatibleTemplateNumberFormat.java
index 04aca2e..9f7dcc8 100644
--- a/src/main/java/freemarker/core/BackwardCompatibleTemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/BackwardCompatibleTemplateNumberFormat.java
@@ -25,6 +25,6 @@ package freemarker.core;
  */
 abstract class BackwardCompatibleTemplateNumberFormat extends TemplateNumberFormat {
 
-    abstract String format(Number number) throws UnformattableNumberException;
+    abstract String format(Number number) throws UnformattableValueException;
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/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 58bcddc..6520720 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -186,7 +186,7 @@ class BuiltInsForMultipleTypes {
             throws TemplateModelException {
                 try {
                     return df.parse(text);
-                } catch (java.text.ParseException e) {
+                } catch (TemplateValueFormatException e) {
                     throw new _TemplateModelException(e,
                             "The string doesn't match the expected date/time/date-time format. "
                             + "The string to parse was: ", new _DelayedJQuote(text), ". ",

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/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 29812a9..da980c7 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1081,7 +1081,7 @@ public final class Environment extends Configurable {
             throws TemplateModelException, _MiscTemplateException {
         try {
             return format.format(number);
-        } catch (UnformattableNumberException e) {
+        } catch (UnformattableValueException e) {
             throw new _MiscTemplateException(exp, e, this,
                     "Failed to format number with ", new _DelayedJQuote(format.getDescription()), ": ",
                     e.getMessage());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
index 5dda644..b26352a 100644
--- a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
@@ -22,6 +22,7 @@ package freemarker.core;
 import java.util.Date;
 import java.util.TimeZone;
 
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
 import freemarker.template.TemplateDateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.utility.DateUtil;
@@ -197,17 +198,23 @@ abstract class ISOLikeTemplateDateFormat  extends TemplateDateFormat {
             DateToISO8601CalendarFactory calendarFactory);
 
     @Override
-    public final Date parse(String s) throws java.text.ParseException {
+    @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN",
+            justification = "Known to use the singleton Boolean-s only")
+    public final Date parse(String s) throws UnparsableValueException {
         CalendarFieldsToDateConverter calToDateConverter = factory.getCalendarFieldsToDateCalculator(env);
         TimeZone tz = forceUTC != Boolean.FALSE ? DateUtil.UTC : timeZone;
-        if (dateType == TemplateDateModel.DATE) {
-            return parseDate(s, tz, calToDateConverter);
-        } else if (dateType == TemplateDateModel.TIME) {
-            return parseTime(s, tz, calToDateConverter);
-        } else if (dateType == TemplateDateModel.DATETIME) {
-            return parseDateTime(s, tz, calToDateConverter);
-        } else {
-            throw new BugException("Unexpected date type: " + dateType);
+        try {
+            if (dateType == TemplateDateModel.DATE) {
+                return parseDate(s, tz, calToDateConverter);
+            } else if (dateType == TemplateDateModel.TIME) {
+                return parseTime(s, tz, calToDateConverter);
+            } else if (dateType == TemplateDateModel.DATETIME) {
+                return parseDateTime(s, tz, calToDateConverter);
+            } else {
+                throw new BugException("Unexpected date type: " + dateType);
+            }
+        } catch (DateParseException e) {
+            throw new UnparsableValueException(e.getMessage(), e);
         }
     }
     
@@ -255,7 +262,7 @@ abstract class ISOLikeTemplateDateFormat  extends TemplateDateFormat {
      */
     @Override
     public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-            MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+            MarkupOutputFormat<MO> outputFormat) throws TemplateValueFormatException, TemplateModelException {
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/main/java/freemarker/core/JavaTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/JavaTemplateDateFormat.java b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
index 747c7b4..8183a88 100644
--- a/src/main/java/freemarker/core/JavaTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
@@ -44,8 +44,12 @@ class JavaTemplateDateFormat extends TemplateDateFormat {
     }
 
     @Override
-    public Date parse(String s) throws ParseException {
-        return javaDateFormat.parse(s);
+    public Date parse(String s) throws UnparsableValueException {
+        try {
+            return javaDateFormat.parse(s);
+        } catch (ParseException e) {
+            throw new UnparsableValueException(e.getMessage(), e);
+        }
     }
 
     @Override
@@ -70,7 +74,7 @@ class JavaTemplateDateFormat extends TemplateDateFormat {
      */
     @Override
     public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-            MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+            MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
         return null;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
index b34414b..4144af8 100644
--- a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
@@ -34,14 +34,14 @@ final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFor
     }
 
     @Override
-    public String format(TemplateNumberModel numberModel) throws UnformattableNumberException, TemplateModelException {
+    public String format(TemplateNumberModel numberModel) throws UnformattableValueException, TemplateModelException {
         Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
         return format(number);
     }
 
     @Override
     public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
-            MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+            MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
         return null;
     }
 
@@ -51,11 +51,11 @@ final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFor
     }
 
     @Override
-    String format(Number number) throws UnformattableNumberException {
+    String format(Number number) throws UnformattableValueException {
         try {
             return javaNumberFormat.format(number);
         } catch (ArithmeticException e) {
-            throw new UnformattableNumberException(
+            throw new UnformattableValueException(
                     "This format can't format the " + number + " number. Reason: " + e.getMessage(), e);
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/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 497ea25..92d63fa 100644
--- a/src/main/java/freemarker/core/TemplateDateFormat.java
+++ b/src/main/java/freemarker/core/TemplateDateFormat.java
@@ -98,7 +98,7 @@ public abstract class TemplateDateFormat extends TemplateValueFormat {
      * 
      * @return The interpretation of the text as {@link Date}. Can't be {@code null}.
      */
-    public abstract Date parse(String s) throws java.text.ParseException;
+    public abstract Date parse(String s) throws TemplateValueFormatException;
     
     /**
      * Tells if this formatter should be re-created if the locale changes.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/main/java/freemarker/core/TemplateFormatUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateFormatUtil.java b/src/main/java/freemarker/core/TemplateFormatUtil.java
index 2856df5..66e10da 100644
--- a/src/main/java/freemarker/core/TemplateFormatUtil.java
+++ b/src/main/java/freemarker/core/TemplateFormatUtil.java
@@ -43,10 +43,10 @@ public final class TemplateFormatUtil {
 
     /**
      * Utility method to extract the {@link Number} from an {@link TemplateNumberModel}, and throw
-     * {@link UnformattableNumberException} with a standard error message if that's {@code null}.
+     * {@link UnformattableValueException} with a standard error message if that's {@code null}.
      */
     public static Number getNonNullNumber(TemplateNumberModel numberModel)
-            throws TemplateModelException, UnformattableNumberException {
+            throws TemplateModelException, UnformattableValueException {
         Number number = numberModel.getAsNumber();
         if (number == null) {
             throw EvalUtil.newModelHasStoredNullException(Number.class, numberModel, null);
@@ -56,7 +56,7 @@ public final class TemplateFormatUtil {
 
     /**
      * Utility method to extract the {@link Date} from an {@link TemplateDateModel}, and throw
-     * {@link UnformattableDateException} with a standard error message if that's {@code null}.
+     * {@link UnformattableValueException} with a standard error message if that's {@code null}.
      */
     public static Date getNonNullDate(TemplateDateModel dateModel) throws TemplateModelException {
         Date date = dateModel.getAsDate();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/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 87d4bbc..ee2ec83 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -50,7 +50,7 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
      * 
      * @throws TemplateValueFormatException
      *             If any problem occurs while parsing/getting the format. Notable subclass:
-     *             {@link UnformattableNumberException}.
+     *             {@link UnformattableValueException}.
      * @throws TemplateModelException
      *             Exception thrown by the {@code dateModel} object when calling its methods.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/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
deleted file mode 100644
index 8173b30..0000000
--- a/src/main/java/freemarker/core/UnformattableDateException.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 freemarker.core;
-
-import freemarker.template.TemplateDateModel;
-
-/**
- * Thrown when a {@link TemplateDateModel} can't be formatted because of the value/properties of the
- * {@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 TemplateValueFormatException {
-
-    public UnformattableDateException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public UnformattableDateException(String message) {
-        super(message);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/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
deleted file mode 100644
index 2342338..0000000
--- a/src/main/java/freemarker/core/UnformattableNumberException.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 freemarker.core;
-
-import freemarker.template.TemplateNumberModel;
-
-/**
- * Thrown when a {@link TemplateNumberModel} can't be formatted because of the value/properties of the
- * {@link TemplateNumberModel}. For example, some formatters might can't format NaN, or can't display numbers above
- * certain magnitude.
- * 
- * @since 2.3.24
- */
-public class UnformattableNumberException extends TemplateValueFormatException {
-
-    public UnformattableNumberException(String message, Throwable cause) {
-        super(message, cause);
-    }
-
-    public UnformattableNumberException(String message) {
-        super(message);
-    }
-
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/main/java/freemarker/core/UnformattableValueException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnformattableValueException.java b/src/main/java/freemarker/core/UnformattableValueException.java
new file mode 100644
index 0000000..d769680
--- /dev/null
+++ b/src/main/java/freemarker/core/UnformattableValueException.java
@@ -0,0 +1,41 @@
+/*
+ * 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;
+
+import freemarker.template.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/03e8fc07/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 45be76a..84275bd 100644
--- a/src/main/java/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
+++ b/src/main/java/freemarker/core/UnknownDateTypeFormattingUnsupportedException.java
@@ -26,7 +26,7 @@ import freemarker.template.TemplateDateModel;
  * 
  * @since 2.3.24
  */
-public final class UnknownDateTypeFormattingUnsupportedException extends UnformattableDateException {
+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/03e8fc07/src/main/java/freemarker/core/UnparsableValueException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnparsableValueException.java b/src/main/java/freemarker/core/UnparsableValueException.java
new file mode 100644
index 0000000..dab0397
--- /dev/null
+++ b/src/main/java/freemarker/core/UnparsableValueException.java
@@ -0,0 +1,38 @@
+/*
+ * 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;
+
+/**
+ * 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/03e8fc07/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 f2f0dae..d527aa0 100644
--- a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
@@ -87,7 +87,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
                 return Integer.toString(NumberUtil.toIntExact(n), base);
             } catch (ArithmeticException e) {
                 if (fallbackFormat == null) {
-                    throw new UnformattableNumberException(
+                    throw new UnformattableValueException(
                             n + " doesn't fit into an int, and there was no fallback format specified.");
                 } else {
                     return fallbackFormat.format(numberModel);
@@ -97,7 +97,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
 
         @Override
         public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             return null;
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java b/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
index 0181452..b056e7b 100644
--- a/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
@@ -18,7 +18,6 @@
  */
 package freemarker.core;
 
-import java.text.ParseException;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -64,7 +63,7 @@ public class EpochMillisDivTemplateDateFormatFactory extends TemplateDateFormatF
         
         @Override
         public String format(TemplateDateModel dateModel)
-                throws UnformattableDateException, TemplateModelException {
+                throws UnformattableValueException, TemplateModelException {
             return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime() / divisor);
         }
 
@@ -80,16 +79,16 @@ public class EpochMillisDivTemplateDateFormatFactory extends TemplateDateFormatF
 
         @Override
         public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             throw new NotImplementedException();
         }
 
         @Override
-        public Date parse(String s) throws ParseException {
+        public Date parse(String s) throws UnparsableValueException {
             try {
                 return new Date(Long.parseLong(s));
             } catch (NumberFormatException e) {
-                throw new ParseException("Malformed long", 0);
+                throw new UnparsableValueException("Malformed long");
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java b/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
index d2e8a0a..176aec7 100644
--- a/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
@@ -18,7 +18,6 @@
  */
 package freemarker.core;
 
-import java.text.ParseException;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -51,7 +50,7 @@ public class EpochMillisTemplateDateFormatFactory extends TemplateDateFormatFact
         
         @Override
         public String format(TemplateDateModel dateModel)
-                throws UnformattableDateException, TemplateModelException {
+                throws UnformattableValueException, TemplateModelException {
             return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime());
         }
 
@@ -67,16 +66,16 @@ public class EpochMillisTemplateDateFormatFactory extends TemplateDateFormatFact
 
         @Override
         public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             throw new NotImplementedException();
         }
 
         @Override
-        public Date parse(String s) throws ParseException {
+        public Date parse(String s) throws UnparsableValueException {
             try {
                 return new Date(Long.parseLong(s));
             } catch (NumberFormatException e) {
-                throw new ParseException("Malformed long", 0);
+                throw new UnparsableValueException("Malformed long");
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
index 8a9ded2..f18d97b 100644
--- a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
@@ -47,18 +47,18 @@ public class HexTemplateNumberFormatFactory extends TemplateNumberFormatFactory
         
         @Override
         public String format(TemplateNumberModel numberModel)
-                throws UnformattableNumberException, TemplateModelException {
+                throws UnformattableValueException, TemplateModelException {
             Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
             try {
                 return Integer.toHexString(NumberUtil.toIntExact(n));
             } catch (ArithmeticException e) {
-                throw new UnformattableNumberException(n + " doesn't fit into an int");
+                throw new UnformattableValueException(n + " doesn't fit into an int");
             }
         }
 
         @Override
         public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             return null;
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java b/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
index a5e4d89..7fd46fe 100644
--- a/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
@@ -18,7 +18,6 @@
  */
 package freemarker.core;
 
-import java.text.ParseException;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -55,7 +54,7 @@ public class LocAndTZSensitiveTemplateDateFormatFactory extends TemplateDateForm
 
         @Override
         public String format(TemplateDateModel dateModel)
-                throws UnformattableDateException, TemplateModelException {
+                throws UnformattableValueException, TemplateModelException {
             return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime() + "@" + locale + ":" + timeZone.getID());
         }
 
@@ -71,20 +70,20 @@ public class LocAndTZSensitiveTemplateDateFormatFactory extends TemplateDateForm
 
         @Override
         public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             throw new NotImplementedException();
         }
 
         @Override
-        public Date parse(String s) throws ParseException {
+        public Date parse(String s) throws UnparsableValueException {
             try {
                 int atIdx = s.indexOf("@");
                 if (atIdx == -1) {
-                    throw new ParseException("Missing @", 0);
+                    throw new UnparsableValueException("Missing @");
                 }
                 return new Date(Long.parseLong(s.substring(0, atIdx)));
             } catch (NumberFormatException e) {
-                throw new ParseException("Malformed long", 0);
+                throw new UnparsableValueException("Malformed long");
             }
         }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/03e8fc07/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
index 01dbc83..3fb2fae 100644
--- a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
@@ -48,18 +48,18 @@ public class LocaleSensitiveTemplateNumberFormatFactory extends TemplateNumberFo
         
         @Override
         public String format(TemplateNumberModel numberModel)
-                throws UnformattableNumberException, TemplateModelException {
+                throws UnformattableValueException, TemplateModelException {
             Number n = numberModel.getAsNumber();
             try {
                 return n + "_" + locale;
             } catch (ArithmeticException e) {
-                throw new UnformattableNumberException(n + " doesn't fit into an int");
+                throw new UnformattableValueException(n + " doesn't fit into an int");
             }
         }
     
         @Override
         public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableNumberException, TemplateModelException {
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             return null;
         }
     


[12/12] incubator-freemarker git commit: Merge remote-tracking branch 'origin/2.3-gae' into 2.3

Posted by dd...@apache.org.
Merge remote-tracking branch 'origin/2.3-gae' into 2.3


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

Branch: refs/heads/2.3
Commit: a0eaf3da9724a063bf04ccc5e5968c843e914b02
Parents: 53bf6b3 f32a506
Author: ddekany <dd...@apache.org>
Authored: Thu Sep 24 22:20:22 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Sep 24 22:20:22 2015 +0200

----------------------------------------------------------------------
 build.xml                                       |  17 +-
 ivy.xml                                         |   5 -
 .../freemarker/core/AddConcatExpression.java    |  56 +-
 src/main/java/freemarker/core/Assignment.java   |   9 +-
 .../BackwardCompatibleTemplateNumberFormat.java |   2 +-
 .../core/BuiltInForLegacyEscaping.java          |  11 +-
 .../core/BuiltInsForMultipleTypes.java          |  55 +-
 .../core/BuiltInsForOutputFormatRelated.java    |  21 +-
 .../core/BuiltInsForStringsEncoding.java        |  25 +
 src/main/java/freemarker/core/Configurable.java |  71 ++-
 .../java/freemarker/core/DollarVariable.java    |  33 +-
 src/main/java/freemarker/core/Environment.java  |  88 +--
 src/main/java/freemarker/core/EvalUtil.java     |  66 ++-
 src/main/java/freemarker/core/Expression.java   |   4 -
 .../core/ISOLikeTemplateDateFormat.java         |  36 +-
 .../freemarker/core/JavaTemplateDateFormat.java |  19 +-
 .../core/JavaTemplateNumberFormat.java          |  12 +-
 src/main/java/freemarker/core/MessageUtil.java  |  31 +-
 .../java/freemarker/core/NumberLiteral.java     |   2 +-
 .../core/ParsingNotSupportedException.java      |  37 ++
 .../freemarker/core/TemplateDateFormat.java     |  74 +--
 .../freemarker/core/TemplateFormatUtil.java     |  15 +-
 .../freemarker/core/TemplateNumberFormat.java   |  66 +--
 .../core/TemplateXHTMLOutputModel.java          |  40 ++
 .../core/UnformattableDateException.java        |  41 --
 .../core/UnformattableNumberException.java      |  41 --
 .../core/UnformattableValueException.java       |  41 ++
 ...nDateTypeFormattingUnsupportedException.java |   2 +-
 ...nownDateTypeParsingUnsupportedException.java |  36 ++
 .../core/UnparsableValueException.java          |  38 ++
 .../java/freemarker/core/XHTMLOutputFormat.java |  73 +++
 .../freemarker/ext/jsp/PageContextFactory.java  |   3 +-
 .../ext/jsp/_FreeMarkerPageContext1.java        |  37 --
 .../java/freemarker/template/Configuration.java |  14 +-
 src/main/javacc/FTL.jj                          |  15 +-
 src/manual/book.xml                             | 578 ++++++++++++++++---
 .../core/AppMetaTemplateDateFormatFactory.java  | 121 ++++
 .../core/BaseNTemplateNumberFormatFactory.java  |  12 +-
 .../java/freemarker/core/DateFormatTest.java    | 112 +++-
 ...EpochMillisDivTemplateDateFormatFactory.java |  17 +-
 .../EpochMillisTemplateDateFormatFactory.java   |  17 +-
 .../core/HexTemplateNumberFormatFactory.java    |  12 +-
 ...AndTZSensitiveTemplateDateFormatFactory.java |  19 +-
 ...aleSensitiveTemplateNumberFormatFactory.java |  12 +-
 .../java/freemarker/core/NumberFormatTest.java  |  73 ++-
 .../PrintfGTemplateNumberFormatFactory.java     | 134 +++++
 .../freemarker/core/XHTMLOutputFormatTest.java  |  60 ++
 .../freemarker/template/ConfigurationTest.java  |  65 +++
 48 files changed, 1736 insertions(+), 632 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/a0eaf3da/build.xml
----------------------------------------------------------------------


[02/12] incubator-freemarker git commit: Figured out how TemplateDateFormat.parse will look. Also, ?date, ?time and ?datetime can now be called like ?date(), etc., which returns the exact object that TemplateDateFormat.parse returns. Also added TemplateN

Posted by dd...@apache.org.
Figured out how TemplateDateFormat.parse will look. Also, ?date, ?time and ?datetime can now be called like ?date(), etc., which returns the exact object that TemplateDateFormat.parse returns. Also added TemplateNumberFormat.parse as a final method that always throws ParsingNotSupportedException, to reserve the place for this method for later versions.


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

Branch: refs/heads/2.3
Commit: 7f591d168a0519bb558860a1fdc61d44384a2c79
Parents: 03e8fc0
Author: ddekany <dd...@apache.org>
Authored: Thu Sep 17 21:34:57 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Sep 17 21:43:22 2015 +0200

----------------------------------------------------------------------
 .../core/BuiltInsForMultipleTypes.java          |  34 +++--
 .../core/ISOLikeTemplateDateFormat.java         |   2 +-
 .../freemarker/core/JavaTemplateDateFormat.java |   2 +-
 src/main/java/freemarker/core/MessageUtil.java  |   4 +
 .../core/ParsingNotSupportedException.java      |  37 ++++++
 .../freemarker/core/TemplateDateFormat.java     |  25 +++-
 .../freemarker/core/TemplateNumberFormat.java   |  11 +-
 ...nownDateTypeParsingUnsupportedException.java |  36 ++++++
 src/manual/book.xml                             |  62 +++++++--
 .../core/AppMetaTemplateDateFormatFactory.java  | 129 +++++++++++++++++++
 .../java/freemarker/core/DateFormatTest.java    |  48 ++++++-
 ...EpochMillisDivTemplateDateFormatFactory.java |   2 +-
 .../EpochMillisTemplateDateFormatFactory.java   |   2 +-
 ...AndTZSensitiveTemplateDateFormatFactory.java |   2 +-
 14 files changed, 367 insertions(+), 29 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/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 6520720..6a6a86e 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -142,7 +142,7 @@ class BuiltInsForMultipleTypes {
             private final String text;
             private final Environment env;
             private final TemplateDateFormat defaultFormat;
-            private Date cachedValue;
+            private TemplateDateModel cachedValue;
             
             DateParser(String text, Environment env)
             throws TemplateException {
@@ -152,8 +152,8 @@ class BuiltInsForMultipleTypes {
             }
             
             public Object exec(List args) throws TemplateModelException {
-                checkMethodArgCount(args, 1);
-                return get((String) args.get(0));
+                checkMethodArgCount(args, 0, 1);
+                return args.size() == 0 ? getAsDateModel() : get((String) args.get(0));
             }
             
             public TemplateModel get(String pattern) throws TemplateModelException {
@@ -164,15 +164,31 @@ class BuiltInsForMultipleTypes {
                     // `e` should always be a TemplateModelException here, but to be sure: 
                     throw _CoreAPI.ensureIsTemplateModelException("Failed to get format", e); 
                 }
-                return new SimpleDate(parse(format), dateType);
+                return toTemplateDateModel(parse(format));
             }
-    
-            public Date getAsDate() throws TemplateModelException {
+
+            private TemplateDateModel toTemplateDateModel(Object date) throws _TemplateModelException {
+                if (date instanceof Date) {
+                    return new SimpleDate((Date) date, dateType);
+                } else {
+                    TemplateDateModel tm = (TemplateDateModel) date;
+                    if (tm.getDateType() != dateType) {
+                        throw new _TemplateModelException("The result of the parsing was of the wrong date type.");
+                    }
+                    return tm;
+                }
+            }
+
+            private TemplateDateModel getAsDateModel() throws TemplateModelException {
                 if (cachedValue == null) {
-                    cachedValue = parse(defaultFormat);
+                    cachedValue = toTemplateDateModel(parse(defaultFormat));
                 }
                 return cachedValue;
             }
+            
+            public Date getAsDate() throws TemplateModelException {
+                return getAsDateModel().getAsDate();
+            }
     
             public int getDateType() {
                 return dateType;
@@ -182,10 +198,10 @@ class BuiltInsForMultipleTypes {
                 return false;
             }
     
-            private Date parse(TemplateDateFormat df)
+            private Object parse(TemplateDateFormat df)
             throws TemplateModelException {
                 try {
-                    return df.parse(text);
+                    return df.parse(text, dateType);
                 } catch (TemplateValueFormatException e) {
                     throw new _TemplateModelException(e,
                             "The string doesn't match the expected date/time/date-time format. "

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
index b26352a..9c96a08 100644
--- a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
@@ -200,7 +200,7 @@ abstract class ISOLikeTemplateDateFormat  extends TemplateDateFormat {
     @Override
     @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN",
             justification = "Known to use the singleton Boolean-s only")
-    public final Date parse(String s) throws UnparsableValueException {
+    public final Date parse(String s, int dateType) throws UnparsableValueException {
         CalendarFieldsToDateConverter calToDateConverter = factory.getCalendarFieldsToDateCalculator(env);
         TimeZone tz = forceUTC != Boolean.FALSE ? DateUtil.UTC : timeZone;
         try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/main/java/freemarker/core/JavaTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/JavaTemplateDateFormat.java b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
index 8183a88..996afcf 100644
--- a/src/main/java/freemarker/core/JavaTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
@@ -44,7 +44,7 @@ class JavaTemplateDateFormat extends TemplateDateFormat {
     }
 
     @Override
-    public Date parse(String s) throws UnparsableValueException {
+    public Date parse(String s, int dateType) throws UnparsableValueException {
         try {
             return javaDateFormat.parse(s);
         } catch (ParseException e) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/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 ad0827a..2875aa3 100644
--- a/src/main/java/freemarker/core/MessageUtil.java
+++ b/src/main/java/freemarker/core/MessageUtil.java
@@ -36,6 +36,10 @@ 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 = 
             "Use ?date, ?time, or ?datetime to tell FreeMarker the exact type.";
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/main/java/freemarker/core/ParsingNotSupportedException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ParsingNotSupportedException.java b/src/main/java/freemarker/core/ParsingNotSupportedException.java
new file mode 100644
index 0000000..7b6e064
--- /dev/null
+++ b/src/main/java/freemarker/core/ParsingNotSupportedException.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;
+
+/**
+ * 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/7f591d16/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 92d63fa..980d543 100644
--- a/src/main/java/freemarker/core/TemplateDateFormat.java
+++ b/src/main/java/freemarker/core/TemplateDateFormat.java
@@ -91,14 +91,29 @@ public abstract class TemplateDateFormat extends TemplateValueFormat {
     }
 
     /**
-     * <b>[Unfinished - will change in 2.3.24 final]</b>.
+     * Parsers a string to date/time/datetime, according to this format. Some format implementations may throw
+     * {@link ParsingNotSupportedException} here. 
      * 
-     * TODO Thrown exceptions.
-     * TODO How can one return a TemplateDateModel instead?
+     * @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 as {@link Date}. Can't be {@code null}.
+     * @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 #format(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 Date parse(String s) throws TemplateValueFormatException;
+    public abstract Object parse(String s, int dateType) throws TemplateValueFormatException;
     
     /**
      * Tells if this formatter should be re-created if the locale changes.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/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 ee2ec83..68d2c4d 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -94,6 +94,15 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
      */
     public abstract boolean isLocaleBound();
 
-    // We don't have parse(...) method, because currently FTL only parses to number with the ArithmeticEngine.
+    /**
+     * 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/7f591d16/src/main/java/freemarker/core/UnknownDateTypeParsingUnsupportedException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnknownDateTypeParsingUnsupportedException.java b/src/main/java/freemarker/core/UnknownDateTypeParsingUnsupportedException.java
new file mode 100644
index 0000000..a8deaf6
--- /dev/null
+++ b/src/main/java/freemarker/core/UnknownDateTypeParsingUnsupportedException.java
@@ -0,0 +1,36 @@
+/*
+ * 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;
+
+import freemarker.template.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/7f591d16/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 5978a6d..b1135ff 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -12292,13 +12292,15 @@ Green Mouse</programlisting>
 </programlisting>
 
           <para>You can also specify the format explicitly like
-          <literal>?datetime.<replaceable>format</replaceable></literal> or
-          <literal>?datetime["<replaceable>format</replaceable>"]</literal>
-          (or
-          <literal>?datetime("<replaceable>format</replaceable>")</literal>
-          for historical reasons), and the same with <literal>?date</literal>
-          and <literal>?time</literal>. For the syntax and meaning of format
-          values see the possible values of the <link
+          <literal>?datetime.<replaceable>format</replaceable></literal> (and
+          hence also as
+          <literal>?datetime["<replaceable>format</replaceable>"]</literal>)
+          or
+          <literal>?datetime("<replaceable>format</replaceable>")</literal>;
+          these three forms do the same. The format can be specified similarly
+          with <literal>?date</literal> and <literal>?time</literal> too. For
+          the syntax and meaning of format values see the possible values of
+          the <link
           linkend="topic.dateTimeFormatSettings"><literal>date_format</literal>,
           <literal>time_format</literal> and
           <literal>datetime_format</literal> settings</link>. Example:</para>
@@ -12324,7 +12326,17 @@ Green Mouse</programlisting>
           value to a real date.</para>
 
           <para>Of course, the format also can be a variable, like in
-          <literal>"<replaceable>...</replaceable>"?datetime[myFormat]</literal>.</para>
+          <literal>"<replaceable>...</replaceable>"?datetime(myFormat)</literal>.</para>
+
+          <para>Note that since 2.3.24, these built-ins can also be called
+          with 0 arguments, like <literal>?date()</literal>. It's almost the
+          same as just writing <literal>?date</literal>. The difference is
+          highly technical and rarely matters: <literal>?date()</literal> and
+          such returns exactly the same Java object that the date parser
+          (<literal>freemarker.core.TemplateDateFormat</literal>
+          implementation) returns, while <literal>?date</literal> without the
+          <literal>()</literal> returns tricky wrapper that's a date and a
+          method and hash on the same time.</para>
         </section>
 
         <section xml:id="ref_builtin_ends_with">
@@ -25529,6 +25541,19 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               setting</link> of the current FreeMarker configuration, as a
               string.</para>
             </listitem>
+
+            <listitem>
+              <para><literal>?date</literal>, <literal>?time</literal> and
+              <literal>?datetime</literal> now can be called as 0 argument
+              method, like <literal>?date()</literal>, etc., which returns the
+              exact object that <literal>TemplateDateFormat.parse</literal>
+              returns, instead of the tricky multi-type object that just using
+              <literal>?date</literal> returns. Because custom
+              <literal>TemplateDateFormat</literal> implementations may return
+              custom <literal>TemplateDateModel</literal> implementations,
+              keeping the exact class can be important in some
+              applications.</para>
+            </listitem>
           </itemizedlist>
         </section>
 
@@ -25864,6 +25889,27 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               locale; this is especially useful for dates, where conventions
               can significantly differ in different countries.</para>
             </listitem>
+
+            <listitem>
+              <para><literal>?date</literal>, <literal>?time</literal> and
+              <literal>?datetime</literal> now can be called as 0 argument
+              method, like <literal>?date()</literal>, etc., which returns the
+              exact object that <literal>TemplateDateFormat.parse</literal>
+              returns, instead of the tricky multi-type object that just using
+              <literal>?date</literal> returns. Because custom
+              <literal>TemplateDateFormat</literal> implementations may return
+              custom <literal>TemplateDateModel</literal> implementations,
+              keeping the exact class can be important in some
+              applications.</para>
+            </listitem>
+
+            <listitem>
+              <para>The <literal>TemplateDateFormat.parse</literal> has been
+              worked out (earlier it was a but different and was marked as
+              draft). <literal>TemplateNumberFormat</literal> has also have a
+              parse method now, though it's just a <literal>final</literal>
+              placeholder now.</para>
+            </listitem>
           </itemizedlist>
         </section>
       </section>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java b/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
new file mode 100644
index 0000000..70a293f
--- /dev/null
+++ b/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
@@ -0,0 +1,129 @@
+/*
+ * 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;
+
+import java.util.Date;
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.commons.lang.NotImplementedException;
+
+import freemarker.template.TemplateDateModel;
+import freemarker.template.TemplateModelException;
+
+public class AppMetaTemplateDateFormatFactory extends TemplateDateFormatFactory {
+
+    public static final AppMetaTemplateDateFormatFactory INSTANCE = new AppMetaTemplateDateFormatFactory();
+    
+    private AppMetaTemplateDateFormatFactory() {
+        // Defined to decrease visibility
+    }
+    
+    @Override
+    public TemplateDateFormat get(String params, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput,
+            Environment env) throws UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException {
+        TemplateFormatUtil.checkHasNoParameters(params);
+        return AppMetaTemplateDateFormat.INSTANCE;
+    }
+
+    private static class AppMetaTemplateDateFormat extends TemplateDateFormat {
+
+        private static final AppMetaTemplateDateFormat INSTANCE = new AppMetaTemplateDateFormat();
+        
+        private AppMetaTemplateDateFormat() { }
+        
+        @Override
+        public String format(TemplateDateModel dateModel)
+                throws UnformattableValueException, TemplateModelException {
+            String result = String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime());
+            if (dateModel instanceof AppMetaTemplateDateModel) {
+                result += "/" + ((AppMetaTemplateDateModel) dateModel).getAppMeta(); 
+            }
+            return result;
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return false;
+        }
+
+        @Override
+        public boolean isTimeZoneBound() {
+            return false;
+        }
+
+        @Override
+        public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
+            throw new NotImplementedException();
+        }
+
+        @Override
+        public Object parse(String s, int dateType) throws UnparsableValueException {
+            int slashIdx = s.indexOf('/');
+            try {
+                if (slashIdx != -1) {
+                    return new AppMetaTemplateDateModel(
+                            new Date(Long.parseLong(s.substring(0,  slashIdx))),
+                            dateType,
+                            s.substring(slashIdx +1));
+                } else {
+                    return new Date(Long.parseLong(s));
+                }
+            } catch (NumberFormatException e) {
+                throw new UnparsableValueException("Malformed long");
+            }
+        }
+
+        @Override
+        public String getDescription() {
+            return "millis since the epoch";
+        }
+        
+    }
+    
+    public static class AppMetaTemplateDateModel implements TemplateDateModel {
+        
+        private final Date date;
+        private final int dateType;
+        private final String appMeta;
+
+        public AppMetaTemplateDateModel(Date date, int dateType, String appMeta) {
+            this.date = date;
+            this.dateType = dateType;
+            this.appMeta = appMeta;
+        }
+
+        @Override
+        public Date getAsDate() throws TemplateModelException {
+            return date;
+        }
+
+        @Override
+        public int getDateType() {
+            return dateType;
+        }
+
+        public String getAppMeta() {
+            return appMeta;
+        }
+        
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/test/java/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/DateFormatTest.java b/src/test/java/freemarker/core/DateFormatTest.java
index e421189..af57f77 100644
--- a/src/test/java/freemarker/core/DateFormatTest.java
+++ b/src/test/java/freemarker/core/DateFormatTest.java
@@ -21,6 +21,7 @@ package freemarker.core;
 import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
+import java.io.IOException;
 import java.sql.Time;
 import java.sql.Timestamp;
 import java.util.Date;
@@ -38,6 +39,7 @@ import freemarker.template.Configuration;
 import freemarker.template.SimpleDate;
 import freemarker.template.Template;
 import freemarker.template.TemplateDateModel;
+import freemarker.template.TemplateException;
 import freemarker.template.TemplateModelException;
 import freemarker.test.TemplateTest;
 
@@ -58,7 +60,8 @@ public class DateFormatTest extends TemplateTest {
         cfg.setCustomDateFormats(ImmutableMap.of(
                 "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
                 "loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
-                "div", EpochMillisDivTemplateDateFormatFactory.INSTANCE));
+                "div", EpochMillisDivTemplateDateFormatFactory.INSTANCE,
+                "appMeta", AppMetaTemplateDateFormatFactory.INSTANCE));
     }
 
     @Test
@@ -351,6 +354,49 @@ public class DateFormatTest extends TemplateTest {
                 "2015-Sep_en 2015-Sep_en_GB 2015-Sep_en_GB 2015-sept._fr_FR 2015-szept.");
     }
     
+    /**
+     * ?date() and such are new in 2.3.24.
+     */
+    @Test
+    public void testZeroArgDateBI() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.setDateFormat("@epoch");
+        cfg.setDateTimeFormat("@epoch");
+        cfg.setTimeFormat("@epoch");
+        
+        addToDataModel("t", String.valueOf(T));
+        
+        assertOutput(
+                "${t?date?string.xs_u} ${t?date()?string.xs_u}",
+                "2015-09-06Z 2015-09-06Z");
+        assertOutput(
+                "${t?time?string.xs_u} ${t?time()?string.xs_u}",
+                "12:00:00Z 12:00:00Z");
+        assertOutput(
+                "${t?datetime?string.xs_u} ${t?datetime()?string.xs_u}",
+                "2015-09-06T12:00:00Z 2015-09-06T12:00:00Z");
+    }
+
+    @Test
+    public void testAppMetaRoundtrip() throws IOException, TemplateException {
+        Configuration cfg = getConfiguration();
+        cfg.setDateFormat("@appMeta");
+        cfg.setDateTimeFormat("@appMeta");
+        cfg.setTimeFormat("@appMeta");
+        
+        addToDataModel("t", String.valueOf(T) + "/foo");
+        
+        assertOutput(
+                "${t?date} ${t?date()}",
+                T + " " + T + "/foo");
+        assertOutput(
+                "${t?time} ${t?time()}",
+                T + " " + T + "/foo");
+        assertOutput(
+                "${t?datetime} ${t?datetime()}",
+                T + " " + T + "/foo");
+    }
+    
     private static class MutableTemplateDateModel implements TemplateDateModel {
         
         private Date date;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java b/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
index b056e7b..55fa980 100644
--- a/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
@@ -84,7 +84,7 @@ public class EpochMillisDivTemplateDateFormatFactory extends TemplateDateFormatF
         }
 
         @Override
-        public Date parse(String s) throws UnparsableValueException {
+        public Date parse(String s, int dateType) throws UnparsableValueException {
             try {
                 return new Date(Long.parseLong(s));
             } catch (NumberFormatException e) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java b/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
index 176aec7..63e1287 100644
--- a/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
@@ -71,7 +71,7 @@ public class EpochMillisTemplateDateFormatFactory extends TemplateDateFormatFact
         }
 
         @Override
-        public Date parse(String s) throws UnparsableValueException {
+        public Date parse(String s, int dateType) throws UnparsableValueException {
             try {
                 return new Date(Long.parseLong(s));
             } catch (NumberFormatException e) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/7f591d16/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java b/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
index 7fd46fe..94edd6e 100644
--- a/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
@@ -75,7 +75,7 @@ public class LocAndTZSensitiveTemplateDateFormatFactory extends TemplateDateForm
         }
 
         @Override
-        public Date parse(String s) throws UnparsableValueException {
+        public Date parse(String s, int dateType) throws UnparsableValueException {
             try {
                 int atIdx = s.indexOf("@");
                 if (atIdx == -1) {


[09/12] incubator-freemarker git commit: Added XHTMLOutputFormat and TemplateXHTMLOutputModel.

Posted by dd...@apache.org.
Added XHTMLOutputFormat and TemplateXHTMLOutputModel.


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

Branch: refs/heads/2.3
Commit: 6b2840c6cf767ed6f7adf86c917ea0d04af4a1f8
Parents: b6bd3c2
Author: ddekany <dd...@apache.org>
Authored: Thu Sep 24 00:54:43 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Sep 24 00:54:43 2015 +0200

----------------------------------------------------------------------
 .../core/BuiltInsForStringsEncoding.java        |  2 +-
 .../core/TemplateXHTMLOutputModel.java          | 40 +++++++++++
 .../java/freemarker/core/XHTMLOutputFormat.java | 73 ++++++++++++++++++++
 src/manual/book.xml                             | 20 ++++++
 .../java/freemarker/core/NumberFormatTest.java  |  6 ++
 .../PrintfGTemplateNumberFormatFactory.java     |  3 +-
 .../freemarker/core/XHTMLOutputFormatTest.java  | 60 ++++++++++++++++
 7 files changed, 201 insertions(+), 3 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6b2840c6/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
index f182430..4f02805 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
@@ -152,7 +152,7 @@ class BuiltInsForStringsEncoding {
 
         @Override
         MarkupOutputFormat getMarkupOutputFormat() {
-            return XMLOutputFormat.INSTANCE; // TODO XHTMLOutputFormat
+            return XHTMLOutputFormat.INSTANCE;
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6b2840c6/src/main/java/freemarker/core/TemplateXHTMLOutputModel.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateXHTMLOutputModel.java b/src/main/java/freemarker/core/TemplateXHTMLOutputModel.java
new file mode 100644
index 0000000..b7564e2
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateXHTMLOutputModel.java
@@ -0,0 +1,40 @@
+/*
+ * 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;
+
+/**
+ * Stores HTML markup to be printed; used with {@link HTMLOutputFormat}.
+ * 
+ * @since 2.3.24
+ */
+public final class TemplateXHTMLOutputModel extends CommonTemplateMarkupOutputModel<TemplateXHTMLOutputModel> {
+    
+    /**
+     * See {@link CommonTemplateMarkupOutputModel#CommonTemplateMarkupOutputModel(String, String)}.
+     */
+    TemplateXHTMLOutputModel(String plainTextContent, String markupContent) {
+        super(plainTextContent, markupContent);
+    }
+
+    @Override
+    public XHTMLOutputFormat getOutputFormat() {
+        return XHTMLOutputFormat.INSTANCE;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6b2840c6/src/main/java/freemarker/core/XHTMLOutputFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/XHTMLOutputFormat.java b/src/main/java/freemarker/core/XHTMLOutputFormat.java
new file mode 100644
index 0000000..e14f597
--- /dev/null
+++ b/src/main/java/freemarker/core/XHTMLOutputFormat.java
@@ -0,0 +1,73 @@
+/*
+ * 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;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import freemarker.template.TemplateModelException;
+import freemarker.template.utility.StringUtil;
+
+/**
+ * Represents the XHTML output format.
+ * 
+ * @since 2.3.24
+ */
+public final class XHTMLOutputFormat extends CommonMarkupOutputFormat<TemplateXHTMLOutputModel> {
+
+    /**
+     * The only instance (singleton) of this {@link OutputFormat}.
+     */
+    public static final XHTMLOutputFormat INSTANCE = new XHTMLOutputFormat();
+    
+    private XHTMLOutputFormat() {
+        // Only to decrease visibility
+    }
+    
+    @Override
+    public String getName() {
+        return "XHTML";
+    }
+
+    @Override
+    public String getMimeType() {
+        return "application/xhtml+xml";
+    }
+
+    @Override
+    public void output(String textToEsc, Writer out) throws IOException, TemplateModelException {
+        StringUtil.XHTMLEnc(textToEsc, out);
+    }
+
+    @Override
+    public String escapePlainText(String plainTextContent) {
+        return StringUtil.XHTMLEnc(plainTextContent);
+    }
+
+    @Override
+    public boolean isLegacyBuiltInBypassed(String builtInName) {
+        return builtInName.equals("html") || builtInName.equals("xml") || builtInName.equals("xhtml");
+    }
+
+    @Override
+    protected TemplateXHTMLOutputModel newTemplateMarkupOutputModel(String plainTextContent, String markupContent) {
+        return new TemplateXHTMLOutputModel(plainTextContent, markupContent);
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6b2840c6/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index a2f415f..d0679f6 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26221,6 +26221,26 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               parse method now, though it's just a <literal>final</literal>
               placeholder now.</para>
             </listitem>
+
+            <listitem>
+              <para><literal>TemplateNumberFormat.format</literal> and
+              <literal>TemplateDateFormat.format</literal> overload that
+              returns markup output is now actually called, so for example now
+              it's possible to use HTML in formatting results. (See
+              <literal>freemarker.core.PrintfGTemplateNumberFormatFactory</literal>
+              under <literal>src/test/java</literal> as an example.) Note that
+              formatting to markup only happens if FTL thinks that the
+              expected result is not just a string, such as for
+              <literal>${<replaceable>myNumber</replaceable>}</literal> if
+              that file has markup <link
+              linkend="dgui_misc_autoescaping_outputformat">output
+              format</link>.</para>
+            </listitem>
+
+            <listitem>
+              <para>Added <literal>XHTMLOutputFormat</literal> and
+              <literal>TemplateXHTMLOutputModel</literal>.</para>
+            </listitem>
           </itemizedlist>
         </section>
       </section>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6b2840c6/src/test/java/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index 8db57dc..46f4d07 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -297,6 +297,12 @@ public class NumberFormatTest extends TemplateTest {
                 "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>");
         assertOutput("<#ftl outputFormat='HTML'>${\"" + commonFTL + "\"}",
                 "1.23E+06 cat:1.23E+06 1.23E-05");
+        assertOutput("<#escape x as x?html>" + commonFTL + "</#escape>",
+                "1.23*10<sup>6</sup> cat:1.23E+06 1.23*10<sup>-5</sup>");
+        assertOutput("<#escape x as x?xhtml>" + commonFTL + "</#escape>",
+                "1.23*10<sup>6</sup> cat:1.23E+06 1.23*10<sup>-5</sup>");
+        assertOutput("<#escape x as x?xml>" + commonFTL + "</#escape>",
+                "1.23E+06 cat:1.23E+06 1.23E-05");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6b2840c6/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
index a44dac3..dd3c2be 100644
--- a/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
@@ -92,8 +92,7 @@ public class PrintfGTemplateNumberFormatFactory extends TemplateNumberFormatFact
         @Override
         public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
                 MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            // TODO XHTMLOutputFormat
-            if (!(outputFormat instanceof HTMLOutputFormat)) {
+            if (!(outputFormat instanceof HTMLOutputFormat || outputFormat instanceof XHTMLOutputFormat)) {
                 return null;
             }
             

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/6b2840c6/src/test/java/freemarker/core/XHTMLOutputFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/XHTMLOutputFormatTest.java b/src/test/java/freemarker/core/XHTMLOutputFormatTest.java
new file mode 100644
index 0000000..81466ef
--- /dev/null
+++ b/src/test/java/freemarker/core/XHTMLOutputFormatTest.java
@@ -0,0 +1,60 @@
+/*
+ * 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;
+
+import static freemarker.core.XHTMLOutputFormat.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+
+import org.junit.Test;
+
+import freemarker.template.TemplateModelException; 
+
+public class XHTMLOutputFormatTest {
+    
+    @Test
+    public void testOutputMO() throws TemplateModelException, IOException {
+       StringWriter out = new StringWriter();
+       INSTANCE.output(INSTANCE.fromPlainTextByEscaping("a'b"), out);
+       assertEquals("a&#39;b", out.toString());
+    }
+    
+    @Test
+    public void testOutputString() throws TemplateModelException, IOException {
+        StringWriter out = new StringWriter();
+        INSTANCE.output("a'b", out);
+        assertEquals("a&#39;b", out.toString());
+    }
+    
+    @Test
+    public void testEscaplePlainText() {
+        assertEquals("", INSTANCE.escapePlainText(""));
+        assertEquals("a", INSTANCE.escapePlainText("a"));
+        assertEquals("&lt;a&amp;b&#39;c&quot;d&gt;", INSTANCE.escapePlainText("<a&b'c\"d>"));
+        assertEquals("&lt;&gt;", INSTANCE.escapePlainText("<>"));
+    }
+    
+    @Test
+    public void testGetMimeType() {
+        assertEquals("application/xhtml+xml", INSTANCE.getMimeType());
+    }
+    
+}


[10/12] incubator-freemarker git commit: (Manual typo)

Posted by dd...@apache.org.
(Manual typo)


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

Branch: refs/heads/2.3
Commit: b9523f6a2c2e95d7a0f7408f0cbffea76bb955a2
Parents: 6b2840c
Author: ddekany <dd...@apache.org>
Authored: Thu Sep 24 21:04:53 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Sep 24 21:04:53 2015 +0200

----------------------------------------------------------------------
 src/manual/book.xml | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b9523f6a/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index d0679f6..10db216 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -14639,7 +14639,7 @@ Extended decimal format: 10<emphasis>_</emphasis>00<emphasis>3</emphasis></progr
                   <td><literal>grp</literal></td>
 
                   <td>Grouping separator character. Note that grouping is
-                  turned on by using <literal>","</literal> in the patter, as
+                  turned on by using <literal>","</literal> in the pattern, as
                   shown in the earlier example. If it's not turned on, this
                   option won't have visible effect.</td>
                 </tr>


[07/12] incubator-freemarker git commit: FreeMarker's JSP support (if it's used) now requires at least JSP 2.0. Earlier it only required JSP 1.1. (Reason: The jsp-api dependency for JSP 1.x, which was needed for building, can't be legally present in the

Posted by dd...@apache.org.
FreeMarker's JSP support (if it's used) now requires at least JSP 2.0. Earlier it only required JSP 1.1. (Reason: The jsp-api dependency for JSP 1.x, which was needed for building, can't be legally present in the Maven Central Repository, nor be provided by freemarker.org.)


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

Branch: refs/heads/2.3
Commit: dde63a1f255ca1d8563d5499ee248a4151b3f52b
Parents: 9c2b9c8
Author: ddekany <dd...@apache.org>
Authored: Sat Sep 19 14:06:50 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sat Sep 19 14:09:57 2015 +0200

----------------------------------------------------------------------
 build.xml                                       | 17 +-------
 ivy.xml                                         |  5 ---
 .../freemarker/ext/jsp/PageContextFactory.java  |  3 +-
 .../ext/jsp/_FreeMarkerPageContext1.java        | 37 ------------------
 src/manual/book.xml                             | 41 ++++++++++----------
 5 files changed, 24 insertions(+), 79 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dde63a1f/build.xml
----------------------------------------------------------------------
diff --git a/build.xml b/build.xml
index e6b84cc..999c88b 100644
--- a/build.xml
+++ b/build.xml
@@ -240,35 +240,22 @@
       verify="yes" stubversion="1.2"
     />
 
-    <ivy:cachepath conf="build.jsp2.0" pathid="ivy.dep.jsp1.2" />
+    <ivy:cachepath conf="build.jsp2.0" pathid="ivy.dep.jsp2.0" />
     <javac srcdir="src/main/java" destdir="build/classes" deprecation="off" 
       debug="on" optimize="off" target="1.5" source="1.5" encoding="utf-8"
       includeantruntime="false"
-      classpathref="ivy.dep.jsp1.2"
+      classpathref="ivy.dep.jsp2.0"
       bootclasspath="${boot.classpath.j2se1.5}"
       includes="
         freemarker/ext/jsp/**,
         freemarker/ext/servlet/**,
         freemarker/cache/WebappTemplateLoader.java"
       excludes="
-        freemarker/ext/jsp/TaglibFactory.java,
-        freemarker/ext/jsp/_FreeMarkerPageContext1.java,
         freemarker/ext/jsp/_FreeMarkerPageContext21.java,
         freemarker/ext/jsp/FreeMarkerJspFactory21.java,
         freemarker/ext/jsp/FreeMarkerJspApplicationContext.java"
     />
     
-    <ivy:cachepath conf="build.jsp1.2" pathid="ivy.dep.jsp1.2" />
-    <javac srcdir="src/main/java" destdir="build/classes" deprecation="off" 
-      debug="on" optimize="off" target="1.5" source="1.5" encoding="utf-8"
-      includeantruntime="false"
-      classpathref="ivy.dep.jsp1.2"
-      bootclasspath="${boot.classpath.j2se1.5}"
-      includes="
-        freemarker/ext/jsp/TaglibFactory.java,
-        freemarker/ext/jsp/_FreeMarkerPageContext1.java"
-    />
-    
     <!-- There's no build.jsp2.0, as those classes are part of the common build subset. -->
     
     <ivy:cachepath conf="build.jsp2.1" pathid="ivy.dep.jsp2.1" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dde63a1f/ivy.xml
----------------------------------------------------------------------
diff --git a/ivy.xml b/ivy.xml
index f672bef..f4b63f8 100644
--- a/ivy.xml
+++ b/ivy.xml
@@ -25,9 +25,6 @@
         description="for building FreeMarker without dependencies that optinal dependencies"
     />
 
-    <conf name="build.jsp1.2" extends="build.base"
-        description="for building FreeMarker with JSP 1.2 support"
-    />
     <conf name="build.jsp2.0" extends="build.base"
         description="for building FreeMarker with JSP 2.0 support"
     />
@@ -93,8 +90,6 @@
       <exclude org="xml-apis" module="xml-apis" />
     </dependency>
     
-    <dependency org="javax.servlet.jsp" name="jsp-api" rev="1.2" conf="build.jsp1.2->default" />
-    <dependency org="javax.servlet" name="servlet-api" rev="2.3" conf="build.jsp1.2->default" />
     <dependency org="javax.servlet.jsp" name="jsp-api" rev="2.0" conf="build.jsp2.0->default; example.struts-webapp->default" />
     <dependency org="javax.servlet" name="servlet-api" rev="2.4" conf="build.jsp2.0->default; example.servlet->default" />
     <dependency org="javax.servlet.jsp" name="jsp-api" rev="2.1" conf="build.jsp2.1->default" />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dde63a1f/src/main/java/freemarker/ext/jsp/PageContextFactory.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/jsp/PageContextFactory.java b/src/main/java/freemarker/ext/jsp/PageContextFactory.java
index 2491c3f..653a805 100644
--- a/src/main/java/freemarker/ext/jsp/PageContextFactory.java
+++ b/src/main/java/freemarker/ext/jsp/PageContextFactory.java
@@ -41,7 +41,8 @@ class PageContextFactory {
                     PageContext.class.getMethod("getExpressionEvaluator", (Class[]) null);
                     return Class.forName("freemarker.ext.jsp._FreeMarkerPageContext2");
                 } catch (NoSuchMethodException e2) {
-                    return Class.forName("freemarker.ext.jsp._FreeMarkerPageContext1");
+                    throw new IllegalStateException(
+                            "Since FreeMarker 2.3.24, JSP support requires at least JSP 2.0.");
                 }
             }
         } catch (ClassNotFoundException e) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dde63a1f/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext1.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext1.java b/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext1.java
deleted file mode 100644
index 5618727..0000000
--- a/src/main/java/freemarker/ext/jsp/_FreeMarkerPageContext1.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 freemarker.ext.jsp;
-
-import freemarker.template.TemplateModelException;
-
-/**
- * Don't use this class; it's only public to work around Google App Engine Java
- * compliance issues. FreeMarker developers only: treat this class as package-visible.
- * 
- * Implementation of PageContext that contains JSP 1.1 specific methods.
- */
-public class _FreeMarkerPageContext1 extends FreeMarkerPageContext {
-
-    public _FreeMarkerPageContext1() throws TemplateModelException {
-        super();
-    }
-
-    public void include (String s, boolean b) {}
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/dde63a1f/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index fe7ab96..a2f415f 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -10928,22 +10928,8 @@ ${bar?trim}</programlisting>
 
           <para>Note that you can use JSP taglibs with FreeMarker even if the
           servlet container has no native JSP support, just make sure that the
-          <literal>javax.servlet.jsp.*</literal> packages for JSP 1.2 (or
-          later) are available to your Web application. If your servlet
-          container comes with JSP 1.1, then you have to obtain the following
-          six classes (for example you can extract them from the jar-s of
-          Tomcat 5.x or Tomcat 4.x), and copy them into your webapp's
-          <literal>WEB-INF/classes/<replaceable>...</replaceable></literal>
-          directory: <literal>javax.servlet.jsp.tagext.IterationTag</literal>,
-          <literal>javax.servlet.jsp.tagext.TryCatchFinally</literal>,
-          <literal>javax.servlet.ServletContextListener</literal>,
-          <literal>javax.servlet.ServletContextAttributeListener</literal>,
-          <literal>javax.servlet.http.HttpSessionAttributeListener</literal>,
-          <literal>javax.servlet.http.HttpSessionListener</literal>. But
-          beware, since containers that come with JSP 1.1 usually use earlier
-          Serlvet versions than 2.3, event listeners will not be supported,
-          and thus JSP 1.2 taglibs that register event listeners will not work
-          properly.</para>
+          <literal>javax.servlet.jsp.*</literal> packages for JSP 2.0 (or
+          later) are available to your Web application.</para>
 
           <para>As of this writing, JSP features up to JSP 2.1 are
           implemented, except the "tag files" feature of JSP 2 (i.e., custom
@@ -25832,9 +25818,21 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
 
           <itemizedlist>
             <listitem>
-              <para><emphasis role="strong">Attention!</emphasis> Raised
-              minimum required Java version from 1.4 to 1.5 (aka. Java
-              5)</para>
+              <para><emphasis role="strong">Attention!</emphasis> FreeMarker
+              now requires at least 1.5 (aka. Java 5). 2.3.24 has only
+              required Java 1.4. (Reason: Without this, new public API-s
+              couldn't use generics, which affect negatively the majority of
+              users, while old installations that are still using 1.4 are
+              unlikely to update FreeMarker anyway.)</para>
+            </listitem>
+
+            <listitem>
+              <para><emphasis>Attention!</emphasis> FreeMarker's JSP support
+              (if it's used) now requires at least JSP 2.0. Earlier it only
+              required JSP 1.1. (Reason: The <literal>jsp-api</literal>
+              dependency for JSP 1.x, which was needed for building, can't be
+              legally present in the Maven Central Repository, nor be provided
+              by freemarker.org.)</para>
             </listitem>
 
             <listitem>
@@ -36213,7 +36211,7 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
         </listitem>
 
         <listitem>
-          <para>For the custom JSP taglib support, you will need JSP 1.2 API
+          <para>For the custom JSP taglib support, you will need JSP 2.0 API
           or later avilable. No JSP implementation is needed, just the API.
           This is already present in pretty much every servlet container. For
           more information please see <link linkend="pgui_misc_servlet">the
@@ -36221,7 +36219,8 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
         </listitem>
 
         <listitem>
-          <para>Jython classes are needed for the Jython wrapper.</para>
+          <para>Jython 2.0 or later classes are needed for the Jython
+          wrapper.</para>
         </listitem>
 
         <listitem>


[11/12] incubator-freemarker git commit: Simplified TemplateValueFormat API-s, because the small performance benefit didn't worth the extra code complexity.

Posted by dd...@apache.org.
Simplified TemplateValueFormat API-s, because the small performance benefit didn't worth the extra code complexity.


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

Branch: refs/heads/2.3
Commit: f32a506a32e6eb381ee9b0badd1e54b56a09b72f
Parents: b9523f6
Author: ddekany <dd...@apache.org>
Authored: Thu Sep 24 21:18:14 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Thu Sep 24 22:18:50 2015 +0200

----------------------------------------------------------------------
 .../core/BuiltInsForMultipleTypes.java          |  2 +-
 .../java/freemarker/core/DollarVariable.java    |  4 +-
 src/main/java/freemarker/core/Environment.java  |  6 +--
 src/main/java/freemarker/core/EvalUtil.java     | 53 +++-----------------
 .../core/ISOLikeTemplateDateFormat.java         | 11 +---
 .../freemarker/core/JavaTemplateDateFormat.java | 11 +---
 .../core/JavaTemplateNumberFormat.java          |  8 +--
 .../freemarker/core/TemplateDateFormat.java     | 47 ++++++-----------
 .../freemarker/core/TemplateNumberFormat.java   | 47 ++++++-----------
 src/manual/book.xml                             | 40 +++++++++++----
 .../core/AppMetaTemplateDateFormatFactory.java  | 10 +---
 .../core/BaseNTemplateNumberFormatFactory.java  | 10 +---
 .../java/freemarker/core/DateFormatTest.java    | 34 ++++++-------
 ...EpochMillisDivTemplateDateFormatFactory.java | 10 +---
 .../EpochMillisTemplateDateFormatFactory.java   | 10 +---
 .../core/HexTemplateNumberFormatFactory.java    |  8 +--
 ...AndTZSensitiveTemplateDateFormatFactory.java | 10 +---
 ...aleSensitiveTemplateNumberFormatFactory.java |  8 +--
 .../java/freemarker/core/NumberFormatTest.java  |  8 +--
 .../PrintfGTemplateNumberFormatFactory.java     | 30 +++++------
 20 files changed, 120 insertions(+), 247 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/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 5a0ee73..aaaaf97 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -578,7 +578,7 @@ class BuiltInsForMultipleTypes {
                                 throw new BugException();
                             }
                         }
-                        cachedValue = defaultFormat.format(dateModel);
+                        cachedValue = defaultFormat.formatToString(dateModel);
                     } catch (TemplateValueFormatException e) {
                         try {
                             throw MessageUtil.newCantFormatDateException(defaultFormat, target, e, true);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/main/java/freemarker/core/DollarVariable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/DollarVariable.java b/src/main/java/freemarker/core/DollarVariable.java
index d08f37c..4331b51 100644
--- a/src/main/java/freemarker/core/DollarVariable.java
+++ b/src/main/java/freemarker/core/DollarVariable.java
@@ -60,7 +60,7 @@ final class DollarVariable extends Interpolation {
         final TemplateModel tm = escapedExpression.eval(env);
         final Writer out = env.getOut();
         final Object moOrStr = EvalUtil.coerceModelToMarkupOutputOrString(
-                tm, escapedExpression, null, markupOutputFormat, out, env);
+                tm, escapedExpression, null, markupOutputFormat, env);
         if (moOrStr instanceof String) {
             final String s = (String) moOrStr;
             if (autoEscape) {
@@ -68,7 +68,7 @@ final class DollarVariable extends Interpolation {
             } else {
                 out.write(s);
             }
-        } else if (moOrStr != null) { // moOrStr wasn't output yet
+        } else { // moOrStr wasn't output yet
             final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr;
             final MarkupOutputFormat moOF = mo.getOutputFormat();
             // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/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 5078eb7..a1073c2 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1049,7 +1049,7 @@ public final class Environment extends Configurable {
             boolean useTempModelExc)
             throws TemplateException {
         try {
-            return format.format(number);
+            return format.formatToString(number);
         } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatNumberException(format, exp, e, useTempModelExc);
         }
@@ -1324,7 +1324,7 @@ public final class Environment extends Configurable {
         TemplateDateFormat format = getTemplateDateFormat(tdm, tdmSourceExpr, useTempModelExc);
         
         try {
-            return format.format(tdm);
+            return format.formatToString(tdm);
         } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatDateException(format, tdmSourceExpr, e, useTempModelExc);
         }
@@ -1347,7 +1347,7 @@ public final class Environment extends Configurable {
                 useTempModelExc);
         
         try {
-            return format.format(tdm);
+            return format.formatToString(tdm);
         } catch (TemplateValueFormatException e) {
             throw MessageUtil.newCantFormatDateException(format, blamedDateSourceExp, e, useTempModelExc);
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/main/java/freemarker/core/EvalUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/EvalUtil.java b/src/main/java/freemarker/core/EvalUtil.java
index 517a29e..c20dcff 100644
--- a/src/main/java/freemarker/core/EvalUtil.java
+++ b/src/main/java/freemarker/core/EvalUtil.java
@@ -19,8 +19,6 @@
 
 package freemarker.core;
 
-import java.io.IOException;
-import java.io.Writer;
 import java.util.Date;
 
 import freemarker.ext.beans.BeanModel;
@@ -356,37 +354,13 @@ class EvalUtil {
 
     static Object coerceModelToMarkupOutputOrString(TemplateModel tm, Expression exp, String seqHint,
             MarkupOutputFormat markupOutputFormat, Environment env) throws TemplateException {
-        try {
-            return coerceModelToMarkupOutputOrString(tm, exp, seqHint, markupOutputFormat, null, env);
-        } catch (IOException e) {
-            throw new BugException("Unexpected exception", e);
-        }
-    }
-
-    static Object coerceModelToMarkupOutputOrString(TemplateModel tm, Expression exp, String seqHint,
-            MarkupOutputFormat markupOutputFormat, Writer out, Environment env) throws TemplateException, IOException {
         if (tm instanceof TemplateNumberModel) {
             TemplateNumberModel tnm = (TemplateNumberModel) tm; 
             TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
             try {
-                if (markupOutputFormat != null) {
-                    // Try to return markup output:
-                    if (out == null) {
-                        TemplateMarkupOutputModel r = format.format(tnm, markupOutputFormat);
-                        if (r != null) {
-                            return r;
-                        }
-                        // Falls through
-                    } else {
-                        if (format.format(tnm, markupOutputFormat, out)) {
-                            return null;
-                        }
-                        // Falls through
-                    }
-                }
-                
-                // Return a String:
-                return format.format(tnm);
+                return markupOutputFormat != null 
+                        ? format.formatToMarkupOrString(tnm, markupOutputFormat)
+                        : format.formatToString(tnm);
             } catch (TemplateValueFormatException e) {
                 throw MessageUtil.newCantFormatNumberException(format, exp, e, false);
             }
@@ -394,24 +368,9 @@ class EvalUtil {
             TemplateDateModel tdm = (TemplateDateModel) tm;
             TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
             try {
-                if (markupOutputFormat != null) {
-                    // Try to return markup output:
-                    if (out == null) {
-                        TemplateMarkupOutputModel r = format.format(tdm, markupOutputFormat);
-                        if (r != null) {
-                            return r;
-                        }
-                        // Falls through
-                    } else {
-                        if (format.format(tdm, markupOutputFormat, out)) {
-                            return null;
-                        }
-                        // Falls through
-                    }
-                }
-                
-                // Return a String:
-                return format.format(tdm);
+                return markupOutputFormat != null
+                        ? format.formatToMarkupOrString(tdm, markupOutputFormat)
+                        : format.formatToString(tdm);
             } catch (TemplateValueFormatException e) {
                 throw MessageUtil.newCantFormatDateException(format, exp, e, false);
             }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
index 9c96a08..b379413 100644
--- a/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/ISOLikeTemplateDateFormat.java
@@ -177,7 +177,7 @@ abstract class ISOLikeTemplateDateFormat  extends TemplateDateFormat {
     }
     
     @Override
-    public final String format(TemplateDateModel dateModel) throws TemplateModelException {
+    public final String formatToString(TemplateDateModel dateModel) throws TemplateModelException {
         final Date date = TemplateFormatUtil.getNonNullDate(dateModel);
         return format(
                 date,
@@ -257,15 +257,6 @@ abstract class ISOLikeTemplateDateFormat  extends TemplateDateFormat {
         return true;
     }
 
-    /**
-     * Always returns {@code null} (there's no markup format).
-     */
-    @Override
-    public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-            MarkupOutputFormat<MO> outputFormat) throws TemplateValueFormatException, TemplateModelException {
-        return null;
-    }
-
     protected abstract boolean isXSMode();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/main/java/freemarker/core/JavaTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/JavaTemplateDateFormat.java b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
index 996afcf..02a1460 100644
--- a/src/main/java/freemarker/core/JavaTemplateDateFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateDateFormat.java
@@ -39,7 +39,7 @@ class JavaTemplateDateFormat extends TemplateDateFormat {
     }
     
     @Override
-    public String format(TemplateDateModel dateModel) throws TemplateModelException {
+    public String formatToString(TemplateDateModel dateModel) throws TemplateModelException {
         return javaDateFormat.format(TemplateFormatUtil.getNonNullDate(dateModel));
     }
 
@@ -69,13 +69,4 @@ class JavaTemplateDateFormat extends TemplateDateFormat {
         return true;
     }
     
-    /**
-     * Always returns {@code null} (there's no markup format).
-     */
-    @Override
-    public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-            MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-        return null;
-    }
-
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
index ccbfb9d..f862c2b 100644
--- a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
@@ -34,18 +34,12 @@ final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFor
     }
 
     @Override
-    public String format(TemplateNumberModel numberModel) throws UnformattableValueException, TemplateModelException {
+    public String formatToString(TemplateNumberModel numberModel) throws UnformattableValueException, TemplateModelException {
         Number number = TemplateFormatUtil.getNonNullNumber(numberModel);
         return format(number);
     }
 
     @Override
-    public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
-            MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-        return null;
-    }
-
-    @Override
     public boolean isLocaleBound() {
         return true;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/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 cb58aac..6a6d66b 100644
--- a/src/main/java/freemarker/core/TemplateDateFormat.java
+++ b/src/main/java/freemarker/core/TemplateDateFormat.java
@@ -19,8 +19,6 @@
 
 package freemarker.core;
 
-import java.io.IOException;
-import java.io.Writer;
 import java.text.DateFormat;
 import java.util.Date;
 
@@ -55,39 +53,26 @@ public abstract class TemplateDateFormat extends TemplateValueFormat {
      * @throws TemplateModelException
      *             Exception thrown by the {@code dateModel} object when calling its methods.
      */
-    public abstract String format(TemplateDateModel dateModel)
+    public abstract String formatToString(TemplateDateModel dateModel)
             throws TemplateValueFormatException, TemplateModelException;
 
     /**
-     * <b>[Not yet used, might changes in 2.3.24 final]</b>
-     * Formats the date/time/dateTime to markup instead of to plain text, or returns {@code null} that will make
-     * 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 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}
-     * instead of returning it. This can be utilized for some optimizatoin. In the case where
-     * {@link #format(TemplateDateModel, MarkupOutputFormat)} would return {@code null}, it returns {@code false}. It
-     * writes to the {@link Writer} exactly if the return value is {@code true}.
+     * 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 #formatToString(TemplateDateModel)} escaped, it must return the {@link String} that
+     * {@link #formatToString(TemplateDateModel)} does.
+     * 
+     * @param outputFormat
+     *            When the result is a {@link TemplateMarkupOutputModel} result, it must be exactly of this output
+     *            format.
      * 
-     * <p>
-     * The default implementation in {@link TemplateNumberFormat} builds on calls
-     * {@link #format(TemplateDateModel, MarkupOutputFormat)} and writes its result to the {@link Writer}.
+     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}. If it's a
+     *         {@link TemplateMarkupOutputModel}, then it must have the output format specified in the
+     *         {@code outputFormat} parameter.
      */
-    public <MO extends TemplateMarkupOutputModel> boolean format(TemplateDateModel dateModel,
-            MarkupOutputFormat<MO> outputFormat, Writer out)
-                    throws TemplateValueFormatException, TemplateModelException, IOException {
-        MO mo = format(dateModel, outputFormat);
-        if (mo == null) {
-            return false;
-        }
-        mo.getOutputFormat().output(mo, out);
-        return true;
+    public Object formatToMarkupOrString(TemplateDateModel dateModel, MarkupOutputFormat<?> outputFormat)
+            throws TemplateValueFormatException, TemplateModelException {
+        return formatToString(dateModel);
     }
 
     /**
@@ -107,7 +92,7 @@ public abstract class TemplateDateFormat extends TemplateValueFormat {
      * 
      * @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 #format(TemplateDateModel)} (so if you format
+     *         meta-information thats also extracted during {@link #formatToString(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

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/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 3c29738..eb75ea7 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -18,8 +18,6 @@
  */
 package freemarker.core;
 
-import java.io.IOException;
-import java.io.Writer;
 import java.text.NumberFormat;
 
 import freemarker.template.TemplateDateModel;
@@ -54,41 +52,28 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
      * @throws TemplateModelException
      *             Exception thrown by the {@code dateModel} object when calling its methods.
      */
-    public abstract String format(TemplateNumberModel numberModel)
+    public abstract String formatToString(TemplateNumberModel numberModel)
             throws TemplateValueFormatException, TemplateModelException;
 
     /**
-     * <b>[Not yet used, might changes in 2.3.24 final]</b>
-     * Formats the number to markup instead of to plain text, or returns {@code null} that will make FreeMarker call
-     * {@link #format(TemplateNumberModel)} and escape its result. If the markup format would be just the result of
-     * {@link #format(TemplateNumberModel)} escaped, it should return {@code null}.
-     */
-    public abstract <MO extends TemplateMarkupOutputModel> MO format(
-            TemplateNumberModel numberModel, MarkupOutputFormat<MO> outputFormat)
-                    throws TemplateValueFormatException, TemplateModelException;
-    
-    /**
-     * <b>[Not yet used, might changes in 2.3.24 final]</b>
-     * Same as {@link #format(TemplateNumberModel, MarkupOutputFormat)}, but prints the result to a {@link Writer}
-     * instead of returning it. This can be utilized for some optimizatoin. In the case where
-     * {@link #format(TemplateNumberModel, MarkupOutputFormat)} would return {@code null}, it returns {@code false}. It
-     * writes to the {@link Writer} exactly if the return value is {@code true}.
+     * 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 #formatToString(TemplateNumberModel)} escaped, it must return the {@link String} that
+     * {@link #formatToString(TemplateNumberModel)} does.
      * 
-     * <p>
-     * The default implementation in {@link TemplateNumberFormat} builds on calls
-     * {@link #format(TemplateNumberModel, MarkupOutputFormat)} and writes its result to the {@link Writer}.
+     * @param outputFormat
+     *            When the result is a {@link TemplateMarkupOutputModel} result, it must be exactly of this output
+     *            format. Not {@code null}.
+     * 
+     * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}. If it's a
+     *         {@link TemplateMarkupOutputModel}, then it must have the output format specified in the
+     *         {@code outputFormat} parameter.
      */
-    public <MO extends TemplateMarkupOutputModel> boolean format(
-            TemplateNumberModel numberModel, MarkupOutputFormat<MO> outputFormat, Writer out)
-                    throws TemplateValueFormatException, TemplateModelException, IOException {
-        MO mo = format(numberModel, outputFormat);
-        if (mo == null) {
-            return false;
-        }
-        mo.getOutputFormat().output(mo, out);
-        return true;
+    public Object formatToMarkupOrString(TemplateNumberModel numberModel, MarkupOutputFormat<?> outputFormat)
+            throws TemplateValueFormatException, TemplateModelException {
+        return formatToString(numberModel);
     }
-
+    
     /**
      * Tells if this formatter should be re-created if the locale changes.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 10db216..cf1efb6 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -25988,6 +25988,21 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                 </listitem>
 
                 <listitem>
+                  <para>It's now possible to have HTML or other markup in
+                  number and date/time/datetime formatting results, like
+                  <literal>1.23*10&lt;sup&gt;6&lt;/sup&gt;</literal>, which
+                  won't be accidentally auto-escaped, as FreeMarker knows that
+                  it's already HTML. (See [TODO] as an example.) Note that
+                  formatting to markup only happens in a context that has a
+                  markup <link
+                  linkend="dgui_misc_autoescaping_outputformat">output
+                  format</link>, otherwise FreeMarker will format to plain
+                  text, just as before. Also note that no out-of-the-box
+                  (non-custom) formatter formats to markup at the
+                  moment.</para>
+                </listitem>
+
+                <listitem>
                   <para>The internal format object caching architecture has
                   been reworked, so that it can handle custom formats too. But
                   this reworking also fixes some bottlenecks under highly
@@ -26223,18 +26238,21 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
-              <para><literal>TemplateNumberFormat.format</literal> and
-              <literal>TemplateDateFormat.format</literal> overload that
-              returns markup output is now actually called, so for example now
-              it's possible to use HTML in formatting results. (See
-              <literal>freemarker.core.PrintfGTemplateNumberFormatFactory</literal>
-              under <literal>src/test/java</literal> as an example.) Note that
-              formatting to markup only happens if FTL thinks that the
-              expected result is not just a string, such as for
-              <literal>${<replaceable>myNumber</replaceable>}</literal> if
-              that file has markup <link
+              <para><literal>TemplateNumberFormat.format</literal>/<literal>TemplateDateFormat.format</literal>
+              overloads were renamed to <literal>formatToString</literal> and
+              <literal>formatToMarkupOrString</literal>. The last has
+              different semantics then its predecessor (which was marked as
+              draft), and it's actually used by FreeMarker. Thus, it's now
+              possible to have HTML or other markup in number and
+              date/time/datetime formatting results, like
+              <literal>1.23*10&lt;sup&gt;6&lt;/sup&gt;</literal>, which won't
+              be accidentally auto-escaped, as FreeMarker knows that it's
+              already HTML. (See [TODO] as an example.) Note that formatting
+              to markup only happens in a context that has a markup <link
               linkend="dgui_misc_autoescaping_outputformat">output
-              format</link>.</para>
+              format</link>, otherwise FreeMarker will format to plain text,
+              just like before. Also note that no out-of-the-box (non-custom)
+              formatter formats to markup at the moment.</para>
             </listitem>
 
             <listitem>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java b/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
index 70a293f..e66e6f4 100644
--- a/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/AppMetaTemplateDateFormatFactory.java
@@ -22,8 +22,6 @@ import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
-import org.apache.commons.lang.NotImplementedException;
-
 import freemarker.template.TemplateDateModel;
 import freemarker.template.TemplateModelException;
 
@@ -49,7 +47,7 @@ public class AppMetaTemplateDateFormatFactory extends TemplateDateFormatFactory
         private AppMetaTemplateDateFormat() { }
         
         @Override
-        public String format(TemplateDateModel dateModel)
+        public String formatToString(TemplateDateModel dateModel)
                 throws UnformattableValueException, TemplateModelException {
             String result = String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime());
             if (dateModel instanceof AppMetaTemplateDateModel) {
@@ -69,12 +67,6 @@ public class AppMetaTemplateDateFormatFactory extends TemplateDateFormatFactory
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            throw new NotImplementedException();
-        }
-
-        @Override
         public Object parse(String s, int dateType) throws UnparsableValueException {
             int slashIdx = s.indexOf('/');
             try {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/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 18a06ea..f66559a 100644
--- a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
@@ -80,7 +80,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
         }
         
         @Override
-        public String format(TemplateNumberModel numberModel)
+        public String formatToString(TemplateNumberModel numberModel)
                 throws TemplateModelException, TemplateValueFormatException {
             Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
             try {
@@ -90,18 +90,12 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
                     throw new UnformattableValueException(
                             n + " doesn't fit into an int, and there was no fallback format specified.");
                 } else {
-                    return fallbackFormat.format(numberModel);
+                    return fallbackFormat.formatToString(numberModel);
                 }
             }
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            return null;
-        }
-
-        @Override
         public boolean isLocaleBound() {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/DateFormatTest.java b/src/test/java/freemarker/core/DateFormatTest.java
index c21b83a..490ba5b 100644
--- a/src/test/java/freemarker/core/DateFormatTest.java
+++ b/src/test/java/freemarker/core/DateFormatTest.java
@@ -266,57 +266,57 @@ public class DateFormatTest extends TemplateTest {
         String dateTimeFormatStr2 = dateTimeFormatStr + "'!'";
         
         assertEquals("2015.09.06. 13:00",
-                env.getTemplateDateFormat(TemplateDateModel.DATETIME, Date.class).format(TM));
+                env.getTemplateDateFormat(TemplateDateModel.DATETIME, Date.class).formatToString(TM));
         assertEquals("2015.09.06. 13:00!",
-                env.getTemplateDateFormat(dateTimeFormatStr2, TemplateDateModel.DATETIME, Date.class).format(TM));
+                env.getTemplateDateFormat(dateTimeFormatStr2, TemplateDateModel.DATETIME, Date.class).formatToString(TM));
         
         assertEquals("2015.09.06. (+0100)",
-                env.getTemplateDateFormat(TemplateDateModel.DATE, Date.class).format(TM));
+                env.getTemplateDateFormat(TemplateDateModel.DATE, Date.class).formatToString(TM));
         assertEquals("2015.09.06. (+0100)!",
-                env.getTemplateDateFormat(dateFormatStr2, TemplateDateModel.DATE, Date.class).format(TM));
+                env.getTemplateDateFormat(dateFormatStr2, TemplateDateModel.DATE, Date.class).formatToString(TM));
         
         assertEquals("13:00",
-                env.getTemplateDateFormat(TemplateDateModel.TIME, Date.class).format(TM));
+                env.getTemplateDateFormat(TemplateDateModel.TIME, Date.class).formatToString(TM));
         assertEquals("13:00!",
-                env.getTemplateDateFormat(timeFormatStr2, TemplateDateModel.TIME, Date.class).format(TM));
+                env.getTemplateDateFormat(timeFormatStr2, TemplateDateModel.TIME, Date.class).formatToString(TM));
         
         assertEquals("2015.09.06. 13:00",
-                env.getTemplateDateFormat(TemplateDateModel.DATETIME, Timestamp.class).format(TM));
+                env.getTemplateDateFormat(TemplateDateModel.DATETIME, Timestamp.class).formatToString(TM));
         assertEquals("2015.09.06. 13:00!",
-                env.getTemplateDateFormat(dateTimeFormatStr2, TemplateDateModel.DATETIME, Timestamp.class).format(TM));
+                env.getTemplateDateFormat(dateTimeFormatStr2, TemplateDateModel.DATETIME, Timestamp.class).formatToString(TM));
 
         assertEquals("2015.09.06. (+0000)",
-                env.getTemplateDateFormat(TemplateDateModel.DATE, java.sql.Date.class).format(TM));
+                env.getTemplateDateFormat(TemplateDateModel.DATE, java.sql.Date.class).formatToString(TM));
         assertEquals("2015.09.06. (+0000)!",
-                env.getTemplateDateFormat(dateFormatStr2, TemplateDateModel.DATE, java.sql.Date.class).format(TM));
+                env.getTemplateDateFormat(dateFormatStr2, TemplateDateModel.DATE, java.sql.Date.class).formatToString(TM));
 
         assertEquals("12:00",
-                env.getTemplateDateFormat(TemplateDateModel.TIME, Time.class).format(TM));
+                env.getTemplateDateFormat(TemplateDateModel.TIME, Time.class).formatToString(TM));
         assertEquals("12:00!",
-                env.getTemplateDateFormat(timeFormatStr2, TemplateDateModel.TIME, Time.class).format(TM));
+                env.getTemplateDateFormat(timeFormatStr2, TemplateDateModel.TIME, Time.class).formatToString(TM));
 
         {
             String dateTimeFormatStrLoc = dateTimeFormatStr + " EEEE";
             // Gets into cache:
             TemplateDateFormat format1
                     = env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class);
-            assertEquals("2015.09.06. 13:00 Sunday", format1.format(TM));
+            assertEquals("2015.09.06. 13:00 Sunday", format1.formatToString(TM));
             // Different locale (not cached):
             assertEquals("2015.09.06. 13:00 Sonntag",
                     env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class,
-                            Locale.GERMANY).format(TM));
+                            Locale.GERMANY).formatToString(TM));
             // Different locale and zone (not cached):
             assertEquals("2015.09.06. 14:00 Sonntag",
                     env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class,
-                            Locale.GERMANY, TimeZone.getTimeZone("GMT+02"), TimeZone.getTimeZone("GMT+03")).format(TM));
+                            Locale.GERMANY, TimeZone.getTimeZone("GMT+02"), TimeZone.getTimeZone("GMT+03")).formatToString(TM));
             // Different locale and zone (not cached):
             assertEquals("2015.09.06. 15:00 Sonntag",
                     env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, java.sql.Date.class,
-                            Locale.GERMANY, TimeZone.getTimeZone("GMT+02"), TimeZone.getTimeZone("GMT+03")).format(TM));
+                            Locale.GERMANY, TimeZone.getTimeZone("GMT+02"), TimeZone.getTimeZone("GMT+03")).formatToString(TM));
             // Check for corrupted cache:
             TemplateDateFormat format2
                     = env.getTemplateDateFormat(dateTimeFormatStrLoc, TemplateDateModel.DATETIME, Date.class);
-            assertEquals("2015.09.06. 13:00 Sunday", format2.format(TM));
+            assertEquals("2015.09.06. 13:00 Sunday", format2.formatToString(TM));
             assertSame(format1, format2);
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java b/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
index 55fa980..1a7bf3d 100644
--- a/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/EpochMillisDivTemplateDateFormatFactory.java
@@ -22,8 +22,6 @@ import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
-import org.apache.commons.lang.NotImplementedException;
-
 import freemarker.template.TemplateDateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.utility.StringUtil;
@@ -62,7 +60,7 @@ public class EpochMillisDivTemplateDateFormatFactory extends TemplateDateFormatF
         }
         
         @Override
-        public String format(TemplateDateModel dateModel)
+        public String formatToString(TemplateDateModel dateModel)
                 throws UnformattableValueException, TemplateModelException {
             return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime() / divisor);
         }
@@ -78,12 +76,6 @@ public class EpochMillisDivTemplateDateFormatFactory extends TemplateDateFormatF
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            throw new NotImplementedException();
-        }
-
-        @Override
         public Date parse(String s, int dateType) throws UnparsableValueException {
             try {
                 return new Date(Long.parseLong(s));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java b/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
index 63e1287..216f8c0 100644
--- a/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/EpochMillisTemplateDateFormatFactory.java
@@ -22,8 +22,6 @@ import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
-import org.apache.commons.lang.NotImplementedException;
-
 import freemarker.template.TemplateDateModel;
 import freemarker.template.TemplateModelException;
 
@@ -49,7 +47,7 @@ public class EpochMillisTemplateDateFormatFactory extends TemplateDateFormatFact
         private EpochMillisTemplateDateFormat() { }
         
         @Override
-        public String format(TemplateDateModel dateModel)
+        public String formatToString(TemplateDateModel dateModel)
                 throws UnformattableValueException, TemplateModelException {
             return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime());
         }
@@ -65,12 +63,6 @@ public class EpochMillisTemplateDateFormatFactory extends TemplateDateFormatFact
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            throw new NotImplementedException();
-        }
-
-        @Override
         public Date parse(String s, int dateType) throws UnparsableValueException {
             try {
                 return new Date(Long.parseLong(s));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
index 8687b58..4b5bf0b 100644
--- a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
@@ -46,7 +46,7 @@ public class HexTemplateNumberFormatFactory extends TemplateNumberFormatFactory
         private HexTemplateNumberFormat() { }
         
         @Override
-        public String format(TemplateNumberModel numberModel)
+        public String formatToString(TemplateNumberModel numberModel)
                 throws UnformattableValueException, TemplateModelException {
             Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
             try {
@@ -57,12 +57,6 @@ public class HexTemplateNumberFormatFactory extends TemplateNumberFormatFactory
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            return null;
-        }
-
-        @Override
         public boolean isLocaleBound() {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java b/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
index 94edd6e..b307f2a 100644
--- a/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
+++ b/src/test/java/freemarker/core/LocAndTZSensitiveTemplateDateFormatFactory.java
@@ -22,8 +22,6 @@ import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
 
-import org.apache.commons.lang.NotImplementedException;
-
 import freemarker.template.TemplateDateModel;
 import freemarker.template.TemplateModelException;
 
@@ -53,7 +51,7 @@ public class LocAndTZSensitiveTemplateDateFormatFactory extends TemplateDateForm
         }
 
         @Override
-        public String format(TemplateDateModel dateModel)
+        public String formatToString(TemplateDateModel dateModel)
                 throws UnformattableValueException, TemplateModelException {
             return String.valueOf(TemplateFormatUtil.getNonNullDate(dateModel).getTime() + "@" + locale + ":" + timeZone.getID());
         }
@@ -69,12 +67,6 @@ public class LocAndTZSensitiveTemplateDateFormatFactory extends TemplateDateForm
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateDateModel dateModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            throw new NotImplementedException();
-        }
-
-        @Override
         public Date parse(String s, int dateType) throws UnparsableValueException {
             try {
                 int atIdx = s.indexOf("@");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
index fb989b9..0b2baef 100644
--- a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
@@ -47,7 +47,7 @@ public class LocaleSensitiveTemplateNumberFormatFactory extends TemplateNumberFo
         }
         
         @Override
-        public String format(TemplateNumberModel numberModel)
+        public String formatToString(TemplateNumberModel numberModel)
                 throws UnformattableValueException, TemplateModelException {
             Number n = numberModel.getAsNumber();
             try {
@@ -58,12 +58,6 @@ public class LocaleSensitiveTemplateNumberFormatFactory extends TemplateNumberFo
         }
     
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
-            return null;
-        }
-    
-        @Override
         public boolean isLocaleBound() {
             return true;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index 46f4d07..2c2fd40 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -151,17 +151,17 @@ public class NumberFormatTest extends TemplateTest {
         TemplateNumberFormat defF = env.getTemplateNumberFormat();
         //
         TemplateNumberFormat explF = env.getTemplateNumberFormat("0.00");
-        assertEquals("1.25", explF.format(new SimpleNumber(1.25)));
+        assertEquals("1.25", explF.formatToString(new SimpleNumber(1.25)));
         //
         TemplateNumberFormat expl2F = env.getTemplateNumberFormat("@loc");
-        assertEquals("1.25_en_US", expl2F.format(new SimpleNumber(1.25)));
+        assertEquals("1.25_en_US", expl2F.formatToString(new SimpleNumber(1.25)));
         
         TemplateNumberFormat explFFr = env.getTemplateNumberFormat("0.00", Locale.FRANCE);
         assertNotSame(explF, explFFr);
-        assertEquals("1,25", explFFr.format(new SimpleNumber(1.25)));
+        assertEquals("1,25", explFFr.formatToString(new SimpleNumber(1.25)));
         //
         TemplateNumberFormat expl2FFr = env.getTemplateNumberFormat("@loc", Locale.FRANCE);
-        assertEquals("1.25_fr_FR", expl2FFr.format(new SimpleNumber(1.25)));
+        assertEquals("1.25_fr_FR", expl2FFr.formatToString(new SimpleNumber(1.25)));
         
         assertSame(env.getTemplateNumberFormat(), defF);
         //

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/f32a506a/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
index dd3c2be..47d46b1 100644
--- a/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
@@ -68,7 +68,7 @@ public class PrintfGTemplateNumberFormatFactory extends TemplateNumberFormatFact
         }
         
         @Override
-        public String format(TemplateNumberModel numberModel)
+        public String formatToString(TemplateNumberModel numberModel)
                 throws UnformattableValueException, TemplateModelException {
             final Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
             
@@ -90,32 +90,32 @@ public class PrintfGTemplateNumberFormatFactory extends TemplateNumberFormatFact
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
-                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
+        public Object formatToMarkupOrString(TemplateNumberModel numberModel, MarkupOutputFormat<?> outputFormat)
+                throws UnformattableValueException, TemplateModelException {
+            String strResult = formatToString(numberModel);
             if (!(outputFormat instanceof HTMLOutputFormat || outputFormat instanceof XHTMLOutputFormat)) {
-                return null;
+                return strResult;
             }
             
-            String s = StringUtil.XHTMLEnc(format(numberModel));
-            int eIdx = s.indexOf('E');
-            if (eIdx == -1) {
-                return outputFormat.fromMarkup(s);
+            int expIdx = strResult.indexOf('E');
+            if (expIdx == -1) {
+                return strResult;
             }
                 
-            String expStr = s.substring(eIdx + 1);
-            int expNumStart = 0;
-            while (expNumStart < expStr.length() && isExpSignificantDigitPrefix(expStr.charAt(expNumStart))) {
-                expNumStart++;
+            String expStr = strResult.substring(expIdx + 1);
+            int expSignifNumBegin = 0;
+            while (expSignifNumBegin < expStr.length() && isExpSignifNumPrefix(expStr.charAt(expSignifNumBegin))) {
+                expSignifNumBegin++;
             }
             
             return outputFormat.fromMarkup(
-                    s.substring(0, eIdx)
+                    strResult.substring(0, expIdx)
                     + "*10<sup>"
-                    + (expStr.charAt(0) == '-' ? "-" : "") + expStr.substring(expNumStart)
+                    + (expStr.charAt(0) == '-' ? "-" : "") + expStr.substring(expSignifNumBegin)
                     + "</sup>");
         }
 
-        private boolean isExpSignificantDigitPrefix(char c) {
+        private boolean isExpSignifNumPrefix(char c) {
             return c == '+' || c == '-' || c == '0';
         }
 


[08/12] incubator-freemarker git commit: Formatting to markup with ${numOrDate} and `something + numOrDate`. Incomplete: (a) String built-ins should fail instead of formatting to plain text; (a) More testing needed, like for TemplateDateFormatter-s

Posted by dd...@apache.org.
Formatting to markup with ${numOrDate} and `something + numOrDate`. Incomplete: (a) String built-ins should fail instead of formatting to plain text; (a) More testing needed, like for TemplateDateFormatter-s


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

Branch: refs/heads/2.3
Commit: b6bd3c2cc0abb871f0a59c1e7115f76e45cfeb53
Parents: dde63a1
Author: ddekany <dd...@apache.org>
Authored: Sun Sep 20 17:51:22 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Wed Sep 23 08:29:25 2015 +0200

----------------------------------------------------------------------
 .../freemarker/core/AddConcatExpression.java    |  56 ++++----
 src/main/java/freemarker/core/Assignment.java   |   9 +-
 .../core/BuiltInForLegacyEscaping.java          |  11 +-
 .../core/BuiltInsForMultipleTypes.java          |  17 ++-
 .../core/BuiltInsForOutputFormatRelated.java    |  21 ++-
 .../core/BuiltInsForStringsEncoding.java        |  25 ++++
 .../java/freemarker/core/DollarVariable.java    |  33 +++--
 src/main/java/freemarker/core/Environment.java  |  52 +++----
 src/main/java/freemarker/core/EvalUtil.java     | 107 ++++++++++++---
 src/main/java/freemarker/core/Expression.java   |   4 -
 .../core/JavaTemplateNumberFormat.java          |   2 +-
 src/main/java/freemarker/core/MessageUtil.java  |  27 +++-
 .../java/freemarker/core/NumberLiteral.java     |   2 +-
 .../freemarker/core/TemplateDateFormat.java     |   4 +-
 .../freemarker/core/TemplateFormatUtil.java     |  13 +-
 .../freemarker/core/TemplateNumberFormat.java   |  12 +-
 src/main/javacc/FTL.jj                          |  15 ++-
 .../core/BaseNTemplateNumberFormatFactory.java  |   2 +-
 .../core/HexTemplateNumberFormatFactory.java    |   2 +-
 ...aleSensitiveTemplateNumberFormatFactory.java |   2 +-
 .../java/freemarker/core/NumberFormatTest.java  |  31 ++++-
 .../PrintfGTemplateNumberFormatFactory.java     | 135 +++++++++++++++++++
 22 files changed, 440 insertions(+), 142 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/AddConcatExpression.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/AddConcatExpression.java b/src/main/java/freemarker/core/AddConcatExpression.java
index 3c12da9..c90cb19 100644
--- a/src/main/java/freemarker/core/AddConcatExpression.java
+++ b/src/main/java/freemarker/core/AddConcatExpression.java
@@ -45,15 +45,17 @@ final class AddConcatExpression extends Expression {
 
     private final Expression left;
     private final Expression right;
+    private final MarkupOutputFormat markupOutputFormat;
 
-    AddConcatExpression(Expression left, Expression right) {
+    AddConcatExpression(Expression left, Expression right, MarkupOutputFormat markupOutputFormat) {
         this.left = left;
         this.right = right;
+        this.markupOutputFormat = markupOutputFormat;
     }
 
     @Override
     TemplateModel _eval(Environment env) throws TemplateException {
-        return _eval(env, this, left, left.eval(env), right, right.eval(env));
+        return _eval(env, this, left, left.eval(env), right, right.eval(env), markupOutputFormat);
     }
 
     /**
@@ -65,7 +67,8 @@ final class AddConcatExpression extends Expression {
     static TemplateModel _eval(Environment env,
             TemplateObject parent,
             Expression leftExp, TemplateModel leftModel,
-            Expression rightExp, TemplateModel rightModel)
+            Expression rightExp, TemplateModel rightModel,
+            MarkupOutputFormat markupOutputFormat)
             throws TemplateModelException, TemplateException, NonStringException {
         if (leftModel instanceof TemplateNumberModel && rightModel instanceof TemplateNumberModel) {
             Number first = EvalUtil.modelToNumber((TemplateNumberModel) leftModel, leftExp);
@@ -75,27 +78,33 @@ final class AddConcatExpression extends Expression {
             return new ConcatenatedSequence((TemplateSequenceModel) leftModel, (TemplateSequenceModel) rightModel);
         } else {
             try {
-                String leftStr = EvalUtil.coerceModelToString(leftModel, leftExp, (String) null, true, env);
-                String rightStr = EvalUtil.coerceModelToString(rightModel, rightExp, (String) null, true, env);
-                
-                if (leftStr == null) {  // Signals that the model is markup output 
-                    if (leftModel instanceof TemplateMarkupOutputModel) {
-                        TemplateMarkupOutputModel<?> leftMO = (TemplateMarkupOutputModel<?>) leftModel; 
-                        if (rightStr == null) {  // Signals that the model is markup output
-                            return concatMarkupOutputs(parent, leftMO, (TemplateMarkupOutputModel) rightModel);
-                        }
-                        return concatMarkupOutputs(parent, leftMO, leftMO.getOutputFormat().fromPlainTextByEscaping(rightStr));
-                    } else {
-                        leftStr = "null";  // For B.C. only; should be an error 
-                        // Falls through
+                Object leftOMOrStr = EvalUtil.coerceModelToMarkupOutputOrString(
+                        leftModel, leftExp, (String) null, markupOutputFormat, env);
+                Object rightOMOrStr = EvalUtil.coerceModelToMarkupOutputOrString(
+                        rightModel, rightExp, (String) null, markupOutputFormat, env);
+                // TODO prove that neither can be null
+
+                if (leftOMOrStr instanceof String) {
+                    if (rightOMOrStr instanceof String) {
+                        return new SimpleScalar(((String) leftOMOrStr).concat((String) rightOMOrStr));
+                    } else { // rightOMOrStr instanceof TemplateMarkupOutputModel
+                        TemplateMarkupOutputModel<?> rightMO = (TemplateMarkupOutputModel<?>) rightOMOrStr; 
+                        return concatMarkupOutputs(parent,
+                                rightMO.getOutputFormat().fromPlainTextByEscaping((String) leftOMOrStr),
+                                rightMO);
+                    }                    
+                } else { // leftOMOrStr instanceof TemplateMarkupOutputModel 
+                    TemplateMarkupOutputModel<?> leftMO = (TemplateMarkupOutputModel<?>) leftOMOrStr; 
+                    if (rightOMOrStr instanceof String) {  // markup output
+                        return concatMarkupOutputs(parent,
+                                leftMO,
+                                leftMO.getOutputFormat().fromPlainTextByEscaping((String) rightOMOrStr));
+                    } else { // rightOMOrStr instanceof TemplateMarkupOutputModel
+                        return concatMarkupOutputs(parent,
+                                leftMO,
+                                (TemplateMarkupOutputModel) rightOMOrStr);
                     }
                 }
-                if (rightStr == null) {  // Signals that the model is markup output
-                    TemplateMarkupOutputModel<?> rightMO = (TemplateMarkupOutputModel<?>) rightModel; 
-                    return concatMarkupOutputs(parent, rightMO.getOutputFormat().fromPlainTextByEscaping(leftStr), rightMO);
-                }
-                
-                return new SimpleScalar(leftStr.concat(rightStr));
             } catch (NonStringOrTemplateOutputException e) {
                 if (leftModel instanceof TemplateHashModel && rightModel instanceof TemplateHashModel) {
                     if (leftModel instanceof TemplateHashModelEx && rightModel instanceof TemplateHashModelEx) {
@@ -161,7 +170,8 @@ final class AddConcatExpression extends Expression {
             String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) {
     	return new AddConcatExpression(
     	left.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
-    	right.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState));
+    	right.deepCloneWithIdentifierReplaced(replacedIdentifier, replacement, replacementState),
+    	markupOutputFormat);
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/Assignment.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Assignment.java b/src/main/java/freemarker/core/Assignment.java
index 0521250..8f5206b 100644
--- a/src/main/java/freemarker/core/Assignment.java
+++ b/src/main/java/freemarker/core/Assignment.java
@@ -41,6 +41,7 @@ final class Assignment extends TemplateElement {
     private final String variableName;
     private final int operatorType;
     private final Expression valueExp;
+    private final MarkupOutputFormat markupOutputFormat;
     private Expression namespaceExp;
 
     static final int NAMESPACE = 1;
@@ -57,7 +58,8 @@ final class Assignment extends TemplateElement {
     Assignment(String variableName,
             int operator,
             Expression valueExp,
-            int scope) {
+            int scope,
+            MarkupOutputFormat markupOutputFormat) {
         this.scope = scope;
         
         this.variableName = variableName;
@@ -93,6 +95,8 @@ final class Assignment extends TemplateElement {
         }
         
         this.valueExp = valueExp;
+        
+        this.markupOutputFormat = markupOutputFormat;
     }
     
     void setNamespaceExp(Expression namespaceExp) {
@@ -165,7 +169,8 @@ final class Assignment extends TemplateElement {
                         throw InvalidReferenceException.getInstance(valueExp, env);
                     }
                 }
-                value = AddConcatExpression._eval(env, namespaceExp, null, lhoValue, valueExp, value);
+                value = AddConcatExpression._eval(env,
+                        namespaceExp, null, lhoValue, valueExp, value, markupOutputFormat);
             } else {  // Numerical operation
                 Number lhoNumber;
                 if (lhoValue instanceof TemplateNumberModel) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java b/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java
index bd1c19e..2ccd491 100644
--- a/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java
+++ b/src/main/java/freemarker/core/BuiltInForLegacyEscaping.java
@@ -31,17 +31,20 @@ abstract class BuiltInForLegacyEscaping extends BuiltInBannedWhenAutoEscaping {
     TemplateModel _eval(Environment env)
     throws TemplateException {
         TemplateModel tm = target.eval(env);
-        String targetString = EvalUtil.coerceModelToString(tm, target, null, true, env);
-        if (targetString == null) {
-            TemplateMarkupOutputModel<?> mo = (TemplateMarkupOutputModel<?>) tm;
+        Object moOrStr = EvalUtil.coerceModelToMarkupOutputOrString(tm, target, null, getMarkupOutputFormat(), env);
+        if (moOrStr instanceof String) {
+            return calculateResult((String) moOrStr, env);
+        } else {
+            TemplateMarkupOutputModel<?> mo = (TemplateMarkupOutputModel<?>) moOrStr;
             if (mo.getOutputFormat().isLegacyBuiltInBypassed(key)) {
                 return mo;
             }
             throw new NonStringException(target, tm, env);
         }
-        return calculateResult(targetString, env);
     }
     
     abstract TemplateModel calculateResult(String s, Environment env) throws TemplateException;
     
+    abstract MarkupOutputFormat getMarkupOutputFormat();
+    
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/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 6a6a86e..5a0ee73 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -560,7 +560,7 @@ class BuiltInsForMultipleTypes {
             private TemplateModel formatWith(String key)
             throws TemplateModelException {
                 try {
-                    return new SimpleScalar(env.formatDate(dateModel, key, target, stringBI.this, true));
+                    return new SimpleScalar(env.formatDateToString(dateModel, key, target, stringBI.this, true));
                 } catch (TemplateException e) {
                     // `e` should always be a TemplateModelException here, but to be sure: 
                     throw _CoreAPI.ensureIsTemplateModelException("Failed to format value", e); 
@@ -580,7 +580,12 @@ class BuiltInsForMultipleTypes {
                         }
                         cachedValue = defaultFormat.format(dateModel);
                     } catch (TemplateValueFormatException e) {
-                        throw MessageUtil.newCantFormatDateException(target, e);
+                        try {
+                            throw MessageUtil.newCantFormatDateException(defaultFormat, target, e, true);
+                        } catch (TemplateException e2) {
+                            // `e` should always be a TemplateModelException here, but to be sure: 
+                            throw _CoreAPI.ensureIsTemplateModelException("Failed to format date/time/datetime", e2); 
+                        }
                     }
                 }
                 return cachedValue;
@@ -633,9 +638,9 @@ class BuiltInsForMultipleTypes {
                 String result;
                 try {
                     if (format instanceof BackwardCompatibleTemplateNumberFormat) {
-                        result = env.formatNumber(number, (BackwardCompatibleTemplateNumberFormat) format, target);
+                        result = env.formatNumberToString(number, (BackwardCompatibleTemplateNumberFormat) format, target);
                     } else {
-                        result = env.formatNumber(numberModel, format, target, true);
+                        result = env.formatNumberToString(numberModel, format, target, true);
                     }
                 } catch (TemplateException e) {
                     // `e` should always be a TemplateModelException here, but to be sure: 
@@ -649,10 +654,10 @@ class BuiltInsForMultipleTypes {
                 if (cachedValue == null) {
                     try {
                         if (defaultFormat instanceof BackwardCompatibleTemplateNumberFormat) {
-                            cachedValue = env.formatNumber(
+                            cachedValue = env.formatNumberToString(
                                     number, (BackwardCompatibleTemplateNumberFormat) defaultFormat, target);
                         } else {
-                            cachedValue = env.formatNumber(numberModel, defaultFormat, target, true);
+                            cachedValue = env.formatNumberToString(numberModel, defaultFormat, target, true);
                         }
                     } catch (TemplateException e) {
                         // `e` should always be a TemplateModelException here, but to be sure: 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
index a9b5d8d..556d12b 100644
--- a/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
+++ b/src/main/java/freemarker/core/BuiltInsForOutputFormatRelated.java
@@ -48,24 +48,24 @@ class BuiltInsForOutputFormatRelated {
         @Override
         protected TemplateModel calculateResult(Environment env) throws TemplateException {
             TemplateModel lhoTM = target.eval(env);
-            String lhoStr = EvalUtil.coerceModelToString(lhoTM, target, null, true, env);
             MarkupOutputFormat contextOF = outputFormat;
-            if (lhoStr == null) { // should indicate that lhoTM is a TemplateMarkupOutputModel
-                TemplateMarkupOutputModel lhoMO;
-                try {
-                    lhoMO = (TemplateMarkupOutputModel) lhoTM;
-                } catch (ClassCastException e) {
+            Object lhoMOOrStr = EvalUtil.coerceModelToMarkupOutputOrString(lhoTM, target, null, contextOF, env);
+            if (lhoMOOrStr instanceof String) { // TemplateMarkupOutputModel
+                return calculateResult((String) lhoMOOrStr, contextOF, env);
+            } else {
+                if (lhoMOOrStr == null) {
                     throw EvalUtil.newModelHasStoredNullException(null, lhoTM, target);
                 }
+                TemplateMarkupOutputModel lhoMO = (TemplateMarkupOutputModel) lhoMOOrStr;
                 MarkupOutputFormat lhoOF = lhoMO.getOutputFormat();
                 // ATTENTION: Keep this logic in sync. with ${...}'s logic!
                 if (lhoOF == contextOF || contextOF.isOutputFormatMixingAllowed()) {
                     // bypass
-                    return lhoTM;
+                    return lhoMO;
                 } else {
                     // ATTENTION: Keep this logic in sync. with ${...}'s logic!
-                    lhoStr = lhoOF.getSourcePlainText(lhoMO);
-                    if (lhoStr == null) {
+                    String lhoPlainTtext = lhoOF.getSourcePlainText(lhoMO);
+                    if (lhoPlainTtext == null) {
                         throw new _TemplateModelException(target,
                                 "The left side operand of ?", key, " is in ", new _DelayedToString(lhoOF),
                                 " format, which differs from the current output format, ",
@@ -73,10 +73,9 @@ class BuiltInsForOutputFormatRelated {
                     }
                     // Here we know that lho is escaped plain text. So we re-escape it to the current format and
                     // bypass it, just as if the two output formats were the same earlier.
-                    return contextOF.fromPlainTextByEscaping(lhoStr);
+                    return contextOF.fromPlainTextByEscaping(lhoPlainTtext);
                 }
             }
-            return calculateResult(lhoStr, contextOF, env);
         }
         
         protected abstract TemplateModel calculateResult(String lho, MarkupOutputFormat outputFormat, Environment env)

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
index c47f9ec..f182430 100644
--- a/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
+++ b/src/main/java/freemarker/core/BuiltInsForStringsEncoding.java
@@ -39,6 +39,11 @@ class BuiltInsForStringsEncoding {
             TemplateModel calculateResult(String s, Environment env) {
                 return new SimpleScalar(StringUtil.HTMLEnc(s));
             }
+
+            @Override
+            MarkupOutputFormat getMarkupOutputFormat() {
+                return HTMLOutputFormat.INSTANCE;
+            }
         }
         
         private final BIBeforeICI2d3d20 prevICIObj = new BIBeforeICI2d3d20();
@@ -55,6 +60,11 @@ class BuiltInsForStringsEncoding {
         public Object getPreviousICIChainMember() {
             return prevICIObj;
         }
+
+        @Override
+        MarkupOutputFormat getMarkupOutputFormat() {
+            return HTMLOutputFormat.INSTANCE;
+        }
     }
 
     static class j_stringBI extends BuiltInForString {
@@ -83,6 +93,11 @@ class BuiltInsForStringsEncoding {
         TemplateModel calculateResult(String s, Environment env) {
             return new SimpleScalar(StringUtil.RTFEnc(s));
         }
+
+        @Override
+        MarkupOutputFormat getMarkupOutputFormat() {
+            return RTFOutputFormat.INSTANCE;
+        }
     }
 
     static class urlBI extends BuiltInForString {
@@ -134,6 +149,11 @@ class BuiltInsForStringsEncoding {
         TemplateModel calculateResult(String s, Environment env) {
             return new SimpleScalar(StringUtil.XHTMLEnc(s));
         }
+
+        @Override
+        MarkupOutputFormat getMarkupOutputFormat() {
+            return XMLOutputFormat.INSTANCE; // TODO XHTMLOutputFormat
+        }
     }
 
     static class xmlBI extends BuiltInForLegacyEscaping {
@@ -141,6 +161,11 @@ class BuiltInsForStringsEncoding {
         TemplateModel calculateResult(String s, Environment env) {
             return new SimpleScalar(StringUtil.XMLEnc(s));
         }
+
+        @Override
+        MarkupOutputFormat getMarkupOutputFormat() {
+            return XMLOutputFormat.INSTANCE;
+        }
     }
 
     // Can't be instantiated

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/DollarVariable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/DollarVariable.java b/src/main/java/freemarker/core/DollarVariable.java
index a277a68..d08f37c 100644
--- a/src/main/java/freemarker/core/DollarVariable.java
+++ b/src/main/java/freemarker/core/DollarVariable.java
@@ -38,15 +38,18 @@ final class DollarVariable extends Interpolation {
     
     /** For OutputFormat-based auto-escaping */
     private final OutputFormat outputFormat;
-    private final MarkupOutputFormat autoEscapeOutputFormat;
+    private final MarkupOutputFormat markupOutputFormat;
+    private final boolean autoEscape;
 
     DollarVariable(
             Expression expression, Expression escapedExpression,
-            OutputFormat outputFormat, MarkupOutputFormat autoEscapeOutputFormat) {
+            OutputFormat outputFormat, boolean autoEscape) {
         this.expression = expression;
         this.escapedExpression = escapedExpression;
         this.outputFormat = outputFormat;
-        this.autoEscapeOutputFormat = autoEscapeOutputFormat;
+        this.markupOutputFormat
+                = (MarkupOutputFormat) (outputFormat instanceof MarkupOutputFormat ? outputFormat : null);
+        this.autoEscape = autoEscape;
     }
 
     /**
@@ -54,26 +57,28 @@ final class DollarVariable extends Interpolation {
      */
     @Override
     void accept(Environment env) throws TemplateException, IOException {
-        TemplateModel tm = escapedExpression.eval(env);
-        Writer out = env.getOut();
-        String s = EvalUtil.coerceModelToString(tm, escapedExpression, null, true, env);
-        if (s != null) {
-            if (autoEscapeOutputFormat != null) {
-                autoEscapeOutputFormat.output(s, out);
+        final TemplateModel tm = escapedExpression.eval(env);
+        final Writer out = env.getOut();
+        final Object moOrStr = EvalUtil.coerceModelToMarkupOutputOrString(
+                tm, escapedExpression, null, markupOutputFormat, out, env);
+        if (moOrStr instanceof String) {
+            final String s = (String) moOrStr;
+            if (autoEscape) {
+                markupOutputFormat.output(s, out);
             } else {
                 out.write(s);
             }
-        } else {
-            TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) tm;
-            MarkupOutputFormat moOF = mo.getOutputFormat();
+        } else if (moOrStr != null) { // moOrStr wasn't output yet
+            final TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) moOrStr;
+            final MarkupOutputFormat moOF = mo.getOutputFormat();
             // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
             if (moOF != outputFormat && !outputFormat.isOutputFormatMixingAllowed()) {
-                String srcPlainText;
+                final String srcPlainText;
                 // ATTENTION: Keep this logic in sync. ?esc/?noEsc's logic!
                 srcPlainText = moOF.getSourcePlainText(mo);
                 if (srcPlainText == null) {
                     throw new _TemplateModelException(escapedExpression,
-                            "Tha value to print is in ", new _DelayedToString(moOF),
+                            "The value to print is in ", new _DelayedToString(moOF),
                             " format, which differs from the current output format, ",
                             new _DelayedToString(outputFormat), ". Format conversion wasn't possible.");
                 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/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 1e6aa73..5078eb7 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1033,20 +1033,9 @@ public final class Environment extends Configurable {
      * @param exp
      *            The blamed expression if an error occurs; it's only needed for better error messages
      */
-    String formatNumber(TemplateNumberModel number, Expression exp, boolean useTempModelExc) throws TemplateException {
-        return formatNumber(number, getTemplateNumberFormat(exp, useTempModelExc), exp, useTempModelExc);
-    }
-
-    /**
-     * Format number with the number format specified as the parameter, with the current locale.
-     * 
-     * @param exp
-     *            The blamed expression if an error occurs; it's only needed for better error messages
-     */
-    String formatNumber(
-            TemplateNumberModel number, String formatString, Expression exp,
-            boolean useTempModelExc) throws TemplateException {
-        return formatNumber(number, getTemplateNumberFormat(formatString, exp, useTempModelExc), exp, useTempModelExc);
+    String formatNumberToString(TemplateNumberModel number, Expression exp, boolean useTempModelExc)
+            throws TemplateException {
+        return formatNumberToString(number, getTemplateNumberFormat(exp, useTempModelExc), exp, useTempModelExc);
     }
 
     /**
@@ -1055,19 +1044,14 @@ public final class Environment extends Configurable {
      * @param exp
      *            The blamed expression if an error occurs; it's only needed for better error messages
      */
-    String formatNumber(
+    String formatNumberToString(
             TemplateNumberModel number, TemplateNumberFormat format, Expression exp,
             boolean useTempModelExc)
             throws TemplateException {
         try {
             return format.format(number);
         } catch (TemplateValueFormatException e) {
-            _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
-                    "Failed to format number with format ", new _DelayedJQuote(format.getDescription()), ": ",
-                    e.getMessage())
-                    .blame(exp); 
-            throw useTempModelExc
-                    ? new _TemplateModelException(e, this, desc) : new _MiscTemplateException(e, this, desc);
+            throw MessageUtil.newCantFormatNumberException(format, exp, e, useTempModelExc);
         }
     }
 
@@ -1077,7 +1061,7 @@ public final class Environment extends Configurable {
      * @param exp
      *            The blamed expression if an error occurs; it's only needed for better error messages
      */
-    String formatNumber(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp)
+    String formatNumberToString(Number number, BackwardCompatibleTemplateNumberFormat format, Expression exp)
             throws TemplateModelException, _MiscTemplateException {
         try {
             return format.format(number);
@@ -1335,18 +1319,14 @@ public final class Environment extends Configurable {
      * @param tdmSourceExpr
      *            The blamed expression if an error occurs; only used for error messages.
      */
-    String formatDate(TemplateDateModel tdm, Expression tdmSourceExpr,
+    String formatDateToString(TemplateDateModel tdm, Expression tdmSourceExpr,
             boolean useTempModelExc) throws TemplateException {
-        Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr);
-        
-        TemplateDateFormat format = getTemplateDateFormat(
-                tdm.getDateType(), date.getClass(), tdmSourceExpr,
-                useTempModelExc);
+        TemplateDateFormat format = getTemplateDateFormat(tdm, tdmSourceExpr, useTempModelExc);
         
         try {
             return format.format(tdm);
         } catch (TemplateValueFormatException e) {
-            throw MessageUtil.newCantFormatDateException(tdmSourceExpr, e);
+            throw MessageUtil.newCantFormatDateException(format, tdmSourceExpr, e, useTempModelExc);
         }
     }
 
@@ -1356,7 +1336,7 @@ public final class Environment extends Configurable {
      * @param blamedFormatterExp
      *            The blamed expression if an error occurs; only used for error messages.
      */
-    String formatDate(TemplateDateModel tdm, String formatString,
+    String formatDateToString(TemplateDateModel tdm, String formatString,
             Expression blamedDateSourceExp, Expression blamedFormatterExp,
             boolean useTempModelExc) throws TemplateException {
         Date date = EvalUtil.modelToDate(tdm, blamedDateSourceExp);
@@ -1369,7 +1349,7 @@ public final class Environment extends Configurable {
         try {
             return format.format(tdm);
         } catch (TemplateValueFormatException e) {
-            throw MessageUtil.newCantFormatDateException(blamedDateSourceExp, e);
+            throw MessageUtil.newCantFormatDateException(format, blamedDateSourceExp, e, useTempModelExc);
         }
     }
 
@@ -1537,6 +1517,16 @@ public final class Environment extends Configurable {
         return getTemplateDateFormatWithoutCache(formatString, dateType, locale, timeZone, zonelessInput);
     }
     
+    TemplateDateFormat getTemplateDateFormat(TemplateDateModel tdm, Expression tdmSourceExpr, boolean useTempModelExc)
+            throws TemplateModelException, TemplateException {
+        Date date = EvalUtil.modelToDate(tdm, tdmSourceExpr);
+        
+        TemplateDateFormat format = getTemplateDateFormat(
+                tdm.getDateType(), date.getClass(), tdmSourceExpr,
+                useTempModelExc);
+        return format;
+    }
+
     /**
      * Same as {@link #getTemplateDateFormat(int, Class)}, but translates the exceptions to {@link TemplateException}-s.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/EvalUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/EvalUtil.java b/src/main/java/freemarker/core/EvalUtil.java
index ff41523..517a29e 100644
--- a/src/main/java/freemarker/core/EvalUtil.java
+++ b/src/main/java/freemarker/core/EvalUtil.java
@@ -19,6 +19,8 @@
 
 package freemarker.core;
 
+import java.io.IOException;
+import java.io.Writer;
 import java.util.Date;
 
 import freemarker.ext.beans.BeanModel;
@@ -44,7 +46,7 @@ class EvalUtil {
     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() { }
     
@@ -341,24 +343,93 @@ class EvalUtil {
         }
     }
 
-    static String coerceModelToString(TemplateModel tm, Expression exp, String seqHint, Environment env) throws TemplateException {
-        return coerceModelToString(tm, exp, seqHint, false, env);
-    }
-    
-    /**
-     * @param allowTOM
-     *            Instead of throwing exception, return {@code null} for a {@link TemplateMarkupOutputModel}.
-     */
     static String coerceModelToString(TemplateModel tm, Expression exp, String seqHint,
-            boolean allowTOM,
             Environment env) throws TemplateException {
         if (tm instanceof TemplateNumberModel) {
-            return env.formatNumber((TemplateNumberModel) tm, exp, false);
+            return env.formatNumberToString((TemplateNumberModel) tm, exp, false);
+        } else if (tm instanceof TemplateDateModel) {
+            return env.formatDateToString((TemplateDateModel) tm, exp, false);
+        } else {
+            return coerceModelToStringCommon(tm, exp, seqHint, false, env);
+        }
+    }
+
+    static Object coerceModelToMarkupOutputOrString(TemplateModel tm, Expression exp, String seqHint,
+            MarkupOutputFormat markupOutputFormat, Environment env) throws TemplateException {
+        try {
+            return coerceModelToMarkupOutputOrString(tm, exp, seqHint, markupOutputFormat, null, env);
+        } catch (IOException e) {
+            throw new BugException("Unexpected exception", e);
+        }
+    }
+
+    static Object coerceModelToMarkupOutputOrString(TemplateModel tm, Expression exp, String seqHint,
+            MarkupOutputFormat markupOutputFormat, Writer out, Environment env) throws TemplateException, IOException {
+        if (tm instanceof TemplateNumberModel) {
+            TemplateNumberModel tnm = (TemplateNumberModel) tm; 
+            TemplateNumberFormat format = env.getTemplateNumberFormat(exp, false);
+            try {
+                if (markupOutputFormat != null) {
+                    // Try to return markup output:
+                    if (out == null) {
+                        TemplateMarkupOutputModel r = format.format(tnm, markupOutputFormat);
+                        if (r != null) {
+                            return r;
+                        }
+                        // Falls through
+                    } else {
+                        if (format.format(tnm, markupOutputFormat, out)) {
+                            return null;
+                        }
+                        // Falls through
+                    }
+                }
+                
+                // Return a String:
+                return format.format(tnm);
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatNumberException(format, exp, e, false);
+            }
         } else if (tm instanceof TemplateDateModel) {
-            return env.formatDate((TemplateDateModel) tm, exp, false);
-        } else if (allowTOM && tm instanceof TemplateMarkupOutputModel) {
-            return null;
-        } else if (tm instanceof TemplateScalarModel) {
+            TemplateDateModel tdm = (TemplateDateModel) tm;
+            TemplateDateFormat format = env.getTemplateDateFormat(tdm, exp, false);
+            try {
+                if (markupOutputFormat != null) {
+                    // Try to return markup output:
+                    if (out == null) {
+                        TemplateMarkupOutputModel r = format.format(tdm, markupOutputFormat);
+                        if (r != null) {
+                            return r;
+                        }
+                        // Falls through
+                    } else {
+                        if (format.format(tdm, markupOutputFormat, out)) {
+                            return null;
+                        }
+                        // Falls through
+                    }
+                }
+                
+                // Return a String:
+                return format.format(tdm);
+            } catch (TemplateValueFormatException e) {
+                throw MessageUtil.newCantFormatDateException(format, exp, e, false);
+            }
+        } else if (tm instanceof TemplateMarkupOutputModel) {
+            return tm;
+        } else { 
+            return coerceModelToStringCommon(tm, exp, seqHint, true, env);
+        }
+    }
+
+    /**
+     * @param supportsTOM
+     *            Whether the caller {@code coerceModelTo...} method could handle a {@link TemplateMarkupOutputModel}.
+     */
+    private static String coerceModelToStringCommon(TemplateModel tm, Expression exp, String seqHint, boolean supportsTOM,
+            Environment env) throws TemplateModelException, InvalidReferenceException, TemplateException,
+                    NonStringOrTemplateOutputException, NonStringException {
+        if (tm instanceof TemplateScalarModel) {
             return modelToString((TemplateScalarModel) tm, exp, env);
         } else if (tm == null) {
             if (env.isClassicCompatible()) {
@@ -399,13 +470,13 @@ class EvalUtil {
                 return _BeansAPI.getAsClassicCompatibleString((BeanModel) tm);
             }
             if (seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)) {
-                if (allowTOM) {
+                if (supportsTOM) {
                     throw new NonStringOrTemplateOutputException(exp, tm, seqHint, env);
                 } else {
                     throw new NonStringException(exp, tm, seqHint, env);
                 }
             } else {
-                if (allowTOM) {
+                if (supportsTOM) {
                     throw new NonStringOrTemplateOutputException(exp, tm, env);
                 } else {
                     throw new NonStringException(exp, tm, env);
@@ -413,7 +484,7 @@ class EvalUtil {
             }
         }
     }
-    
+
     /**
      * Returns an {@link ArithmeticEngine} even if {@code env} is {@code null}, because we are in parsing phase.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/Expression.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Expression.java b/src/main/java/freemarker/core/Expression.java
index fef08cd..c1d5355 100644
--- a/src/main/java/freemarker/core/Expression.java
+++ b/src/main/java/freemarker/core/Expression.java
@@ -93,10 +93,6 @@ abstract public class Expression extends TemplateObject {
         return EvalUtil.coerceModelToString(eval(env), this, seqTip, env);
     }
     
-    static String coerceModelToString(TemplateModel tm, Expression exp, Environment env) throws TemplateException {
-        return EvalUtil.coerceModelToString(tm, exp, null, env);
-    }
-    
     Number evalToNumber(Environment env) throws TemplateException {
         TemplateModel model = eval(env);
         return modelToNumber(model, env);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
index 4144af8..ccbfb9d 100644
--- a/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/JavaTemplateNumberFormat.java
@@ -40,7 +40,7 @@ final class JavaTemplateNumberFormat extends BackwardCompatibleTemplateNumberFor
     }
 
     @Override
-    public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
+    public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
             MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
         return null;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/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 2875aa3..3aebacc 100644
--- a/src/main/java/freemarker/core/MessageUtil.java
+++ b/src/main/java/freemarker/core/MessageUtil.java
@@ -296,13 +296,28 @@ class MessageUtil {
                 .tips(MessageUtil.UNKNOWN_DATE_TO_STRING_TIPS));
     }
 
-    static TemplateModelException newCantFormatDateException(
-            Expression dateSourceExpr, TemplateValueFormatException cause) {
-        return new _TemplateModelException(cause, null, new _ErrorDescriptionBuilder(
-                cause.getMessage())
-                .blame(dateSourceExpr));
+    static TemplateException newCantFormatDateException(TemplateDateFormat format, Expression dataSrcExp,
+            TemplateValueFormatException e, boolean useTempModelExc) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Failed to format date/time/datetime with format ", new _DelayedJQuote(format.getDescription()), ": ",
+                e.getMessage())
+                .blame(dataSrcExp); 
+        return useTempModelExc
+                ? new _TemplateModelException(e, (Environment) null, desc)
+                : new _MiscTemplateException(e, (Environment) null, desc);
     }
-
+    
+    static TemplateException newCantFormatNumberException(TemplateNumberFormat format, Expression dataSrcExp,
+            TemplateValueFormatException e, boolean useTempModelExc) {
+        _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
+                "Failed to format number with format ", new _DelayedJQuote(format.getDescription()), ": ",
+                e.getMessage())
+                .blame(dataSrcExp); 
+        return useTempModelExc
+                ? new _TemplateModelException(e, (Environment) null, desc)
+                : new _MiscTemplateException(e, (Environment) null, desc);
+    }
+    
     /**
      * @return "a" or "an" or "a(n)" (or "" for empty string) for an FTL type name
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/NumberLiteral.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/NumberLiteral.java b/src/main/java/freemarker/core/NumberLiteral.java
index f008aa9..91ded3c 100644
--- a/src/main/java/freemarker/core/NumberLiteral.java
+++ b/src/main/java/freemarker/core/NumberLiteral.java
@@ -43,7 +43,7 @@ final class NumberLiteral extends Expression implements TemplateNumberModel {
 
     @Override
     public String evalAndCoerceToString(Environment env) throws TemplateException {
-        return env.formatNumber(this, this, false);
+        return env.formatNumberToString(this, this, false);
     }
 
     public Number getAsNumber() {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/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 980d543..cb58aac 100644
--- a/src/main/java/freemarker/core/TemplateDateFormat.java
+++ b/src/main/java/freemarker/core/TemplateDateFormat.java
@@ -43,11 +43,11 @@ public abstract class TemplateDateFormat extends TemplateValueFormat {
     
     /**
      * @param dateModel
-     *            The date/time/dateTime to format. Most implementations will just work with the return value of
+     *            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}.
+     * @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:

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/java/freemarker/core/TemplateFormatUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/TemplateFormatUtil.java b/src/main/java/freemarker/core/TemplateFormatUtil.java
index 66e10da..034d5cb 100644
--- a/src/main/java/freemarker/core/TemplateFormatUtil.java
+++ b/src/main/java/freemarker/core/TemplateFormatUtil.java
@@ -20,11 +20,14 @@ package freemarker.core;
 
 import java.util.Date;
 
+import freemarker.template.ObjectWrapper;
 import freemarker.template.TemplateDateModel;
 import freemarker.template.TemplateModelException;
 import freemarker.template.TemplateNumberModel;
 
 /**
+ * Utility classes for implementing {@link TemplateValueFormat}-s.
+ * 
  * @since 2.3.24 
  */
 public final class TemplateFormatUtil {
@@ -42,8 +45,10 @@ public final class TemplateFormatUtil {
     }
 
     /**
-     * Utility method to extract the {@link Number} from an {@link TemplateNumberModel}, and throw
-     * {@link UnformattableValueException} with a standard error message if that's {@code null}.
+     * 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 {
@@ -56,7 +61,9 @@ public final class TemplateFormatUtil {
 
     /**
      * Utility method to extract the {@link Date} from an {@link TemplateDateModel}, and throw
-     * {@link UnformattableValueException} with a standard error message if that's {@code null}.
+     * {@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();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/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 68d2c4d..3c29738 100644
--- a/src/main/java/freemarker/core/TemplateNumberFormat.java
+++ b/src/main/java/freemarker/core/TemplateNumberFormat.java
@@ -42,11 +42,11 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
 
     /**
      * @param numberModel
-     *            The date/time/dateTime to format. Most implementations will just work with the return value of
+     *            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 date/time/dateTime as text, with no escaping (like no HTML escaping). Can't be {@code null}.
+     *            
+     * @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:
@@ -64,7 +64,7 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
      * {@link #format(TemplateNumberModel)} escaped, it should return {@code null}.
      */
     public abstract <MO extends TemplateMarkupOutputModel> MO format(
-            TemplateNumberModel dateModel, MarkupOutputFormat<MO> outputFormat)
+            TemplateNumberModel numberModel, MarkupOutputFormat<MO> outputFormat)
                     throws TemplateValueFormatException, TemplateModelException;
     
     /**
@@ -79,9 +79,9 @@ public abstract class TemplateNumberFormat extends TemplateValueFormat {
      * {@link #format(TemplateNumberModel, MarkupOutputFormat)} and writes its result to the {@link Writer}.
      */
     public <MO extends TemplateMarkupOutputModel> boolean format(
-            TemplateNumberModel dateModel, MarkupOutputFormat<MO> outputFormat, Writer out)
+            TemplateNumberModel numberModel, MarkupOutputFormat<MO> outputFormat, Writer out)
                     throws TemplateValueFormatException, TemplateModelException, IOException {
-        MO mo = format(dateModel, outputFormat);
+        MO mo = format(numberModel, outputFormat);
         if (mo == null) {
             return false;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/main/javacc/FTL.jj
----------------------------------------------------------------------
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index c36bad7..33c8ecb 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -326,6 +326,10 @@ public class FMParser {
             autoEscaping = false;
         }
     }
+    
+    MarkupOutputFormat getMarkupOutputFormat() {
+        return outputFormat instanceof MarkupOutputFormat ? (MarkupOutputFormat) outputFormat : null;
+    }
 
     /**
      * Don't use it, unless you are developing FreeMarker itself.
@@ -1661,7 +1665,7 @@ Expression AdditiveExpression() :
             if (plus) {
 	            // plus is treated separately, since it is also
 	            // used for concatenation.
-                result = new AddConcatExpression(lhs, rhs);
+                result = new AddConcatExpression(lhs, rhs, getMarkupOutputFormat());
             } else {
                 numberLiteralOnly(lhs);
                 numberLiteralOnly(rhs);
@@ -2322,8 +2326,7 @@ DollarVariable StringOutput() :
         DollarVariable result = new DollarVariable(
                 exp, escapedExpression(exp),
                 outputFormat,
-                autoEscaping && outputFormat instanceof MarkupOutputFormat
-                        ? (MarkupOutputFormat)  outputFormat : null);
+                autoEscaping);
         result.setLocation(template, begin, end);
         return result;
     }
@@ -2930,7 +2933,7 @@ TemplateElement Assign() :
 		        )
 	        )
 	        {
-	            ass = new Assignment(varName, equalsOp.kind, exp, scope);
+	            ass = new Assignment(varName, equalsOp.kind, exp, scope, getMarkupOutputFormat());
                 if (exp != null) {
                    ass.setLocation(template, nameExp, exp);
                 } else {
@@ -2970,7 +2973,7 @@ TemplateElement Assign() :
 	                )
 	            )
 	            {
-	                ass = new Assignment(varName, equalsOp.kind, exp, scope);
+	                ass = new Assignment(varName, equalsOp.kind, exp, scope, getMarkupOutputFormat());
 	                if (exp != null) {
 	                   ass.setLocation(template, nameExp, exp);
 	                } else {
@@ -3043,7 +3046,7 @@ TemplateElement Assign() :
 	        {
 	            BlockAssignment ba = new BlockAssignment(
 	                   block, varName, scope, nsExp,
-	                   outputFormat instanceof MarkupOutputFormat ? (MarkupOutputFormat) outputFormat : null);
+	                   getMarkupOutputFormat());
 	            ba.setLocation(template, start, end);
 	            return ba;
 	        }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/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 d527aa0..18a06ea 100644
--- a/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/BaseNTemplateNumberFormatFactory.java
@@ -96,7 +96,7 @@ public class BaseNTemplateNumberFormatFactory extends TemplateNumberFormatFactor
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
+        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
                 MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             return null;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
index f18d97b..8687b58 100644
--- a/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/HexTemplateNumberFormatFactory.java
@@ -57,7 +57,7 @@ public class HexTemplateNumberFormatFactory extends TemplateNumberFormatFactory
         }
 
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
+        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
                 MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             return null;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
index 3fb2fae..fb989b9 100644
--- a/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
+++ b/src/test/java/freemarker/core/LocaleSensitiveTemplateNumberFormatFactory.java
@@ -58,7 +58,7 @@ public class LocaleSensitiveTemplateNumberFormatFactory extends TemplateNumberFo
         }
     
         @Override
-        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel dateModel,
+        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
                 MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
             return null;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index b30b72a..8db57dc 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -22,6 +22,8 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
+import java.math.BigDecimal;
+import java.math.BigInteger;
 import java.util.Collections;
 import java.util.Locale;
 import java.util.Map;
@@ -56,7 +58,8 @@ public class NumberFormatTest extends TemplateTest {
         cfg.setCustomNumberFormats(ImmutableMap.of(
                 "hex", HexTemplateNumberFormatFactory.INSTANCE,
                 "loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE,
-                "base", BaseNTemplateNumberFormatFactory.INSTANCE));
+                "base", BaseNTemplateNumberFormatFactory.INSTANCE,
+                "printfG", PrintfGTemplateNumberFormatFactory.INSTANCE));
     }
 
     @Test
@@ -283,6 +286,32 @@ public class NumberFormatTest extends TemplateTest {
                 "1.0_en 1.0_en_GB 1.0_en_GB 1,0_fr_FR 1,0");
     }
     
+    @Test
+    public void testMarkupFormat() throws IOException, TemplateException {
+        getConfiguration().setNumberFormat("@printfG_3");
+
+        String commonFTL = "${1234567} ${'cat:' + 1234567} ${0.0000123}";
+        assertOutput(commonFTL,
+                "1.23E+06 cat:1.23E+06 1.23E-05");
+        assertOutput("<#ftl outputFormat='HTML'>" + commonFTL,
+                "1.23*10<sup>6</sup> cat:1.23*10<sup>6</sup> 1.23*10<sup>-5</sup>");
+        assertOutput("<#ftl outputFormat='HTML'>${\"" + commonFTL + "\"}",
+                "1.23E+06 cat:1.23E+06 1.23E-05");
+    }
+
+    @Test
+    public void testPrintG() throws IOException, TemplateException {
+        for (Number n : new Number[] {
+                1234567, 1234567L, 1234567d, 1234567f, BigInteger.valueOf(1234567), BigDecimal.valueOf(1234567) }) {
+            addToDataModel("n", n);
+            
+            assertOutput("${n?string.@printfG}", "1.23457E+06");
+            assertOutput("${n?string.@printfG_3}", "1.23E+06");
+            assertOutput("${n?string.@printfG_7}", "1234567");
+            assertOutput("${0.0000123?string.@printfG}", "1.23000E-05");
+        }
+    }
+    
     private static class MutableTemplateNumberModel implements TemplateNumberModel {
         
         private Number number;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/b6bd3c2c/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
new file mode 100644
index 0000000..a44dac3
--- /dev/null
+++ b/src/test/java/freemarker/core/PrintfGTemplateNumberFormatFactory.java
@@ -0,0 +1,135 @@
+/*
+ * 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;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.Locale;
+
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.utility.StringUtil;
+
+/**
+ * Formats like {@code %G} in {@code printf}, with the specified number of significant digits. Also has special
+ * formatter for HTML output format, where it uses the HTML "sup" element for exponents.
+ */
+public class PrintfGTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
+
+    public static final PrintfGTemplateNumberFormatFactory INSTANCE = new PrintfGTemplateNumberFormatFactory();
+    
+    private PrintfGTemplateNumberFormatFactory() {
+        // Defined to decrease visibility
+    }
+    
+    @Override
+    public TemplateNumberFormat get(String params, Locale locale, Environment env)
+            throws InvalidFormatParametersException {
+        Integer significantDigits;
+        if (!params.isEmpty()) {
+            try {
+                significantDigits = Integer.valueOf(params);
+            } catch (NumberFormatException e) {
+                throw new InvalidFormatParametersException(
+                        "The format parameter must be an integer, but was (shown quoted) "
+                        + StringUtil.jQuote(params) + ".");
+            }
+        } else {
+            // Use the default of %G
+            significantDigits = null;
+        }
+        return new PrintfGTemplateNumberFormat(significantDigits, locale);
+    }
+
+    private static class PrintfGTemplateNumberFormat extends TemplateNumberFormat {
+        
+        private final Locale locale;
+        private final String printfFormat; 
+
+        private PrintfGTemplateNumberFormat(Integer significantDigits, Locale locale) {
+            this.printfFormat = "%" + (significantDigits != null ? "." + significantDigits : "") + "G";
+            this.locale = locale;
+        }
+        
+        @Override
+        public String format(TemplateNumberModel numberModel)
+                throws UnformattableValueException, TemplateModelException {
+            final Number n = TemplateFormatUtil.getNonNullNumber(numberModel);
+            
+            // printf %G only accepts Double, BigDecimal and Float 
+            final Number gCompatibleN;
+            if (n instanceof Double  || n instanceof BigDecimal || n instanceof Float) {
+                gCompatibleN = n;
+            } else {
+                if (n instanceof BigInteger) {
+                    gCompatibleN = new BigDecimal((BigInteger) n);                        
+                } else if (n instanceof Long) {
+                    gCompatibleN = BigDecimal.valueOf(((Long) n).longValue());
+                } else {
+                    gCompatibleN = Double.valueOf(n.doubleValue());
+                }
+            }
+            
+            return String.format(locale, printfFormat, gCompatibleN);
+        }
+
+        @Override
+        public <MO extends TemplateMarkupOutputModel> MO format(TemplateNumberModel numberModel,
+                MarkupOutputFormat<MO> outputFormat) throws UnformattableValueException, TemplateModelException {
+            // TODO XHTMLOutputFormat
+            if (!(outputFormat instanceof HTMLOutputFormat)) {
+                return null;
+            }
+            
+            String s = StringUtil.XHTMLEnc(format(numberModel));
+            int eIdx = s.indexOf('E');
+            if (eIdx == -1) {
+                return outputFormat.fromMarkup(s);
+            }
+                
+            String expStr = s.substring(eIdx + 1);
+            int expNumStart = 0;
+            while (expNumStart < expStr.length() && isExpSignificantDigitPrefix(expStr.charAt(expNumStart))) {
+                expNumStart++;
+            }
+            
+            return outputFormat.fromMarkup(
+                    s.substring(0, eIdx)
+                    + "*10<sup>"
+                    + (expStr.charAt(0) == '-' ? "-" : "") + expStr.substring(expNumStart)
+                    + "</sup>");
+        }
+
+        private boolean isExpSignificantDigitPrefix(char c) {
+            return c == '+' || c == '-' || c == '0';
+        }
+
+        @Override
+        public boolean isLocaleBound() {
+            return true;
+        }
+
+        @Override
+        public String getDescription() {
+            return "printf " + printfFormat;
+        }
+        
+    }
+
+}


[04/12] incubator-freemarker git commit: In number/date/time/datetime format strings, initial @ can be used to refer to custom formats even if incompatible_improvements is less than 2.3.24, as far as there's any custom format defined. As custom formats i

Posted by dd...@apache.org.
In number/date/time/datetime format strings, initial @ can be used to refer to custom formats even if incompatible_improvements is less than 2.3.24, as far as there's any custom format defined. As custom formats is also a 2.3.24 feature, it's backward compatible this way.


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

Branch: refs/heads/2.3
Commit: d5bfedbad0169fbf26ba34c0b43167d433f22cc0
Parents: ee7f23f
Author: ddekany <dd...@apache.org>
Authored: Fri Sep 18 20:13:35 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Sep 18 20:13:35 2015 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java | 47 +++++++++----
 src/main/java/freemarker/core/Environment.java  |  8 +--
 .../java/freemarker/template/Configuration.java | 14 +++-
 src/manual/book.xml                             | 72 +++++++++++---------
 .../java/freemarker/core/DateFormatTest.java    | 24 +++++--
 .../java/freemarker/core/NumberFormatTest.java  | 22 ++++--
 .../freemarker/template/ConfigurationTest.java  | 65 ++++++++++++++++++
 7 files changed, 194 insertions(+), 58 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 44e3fcb..bdc76ae 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -726,12 +726,12 @@ public class Configurable {
      *       rounding mode, and {@code _} as the group separator. See more about "extended Java decimal format" in the
      *       FreeMarker Manual.
      *       </li>
-     *   <li>If the string starts with {@code @} character, and
-     *       {@link Configuration#setIncompatibleImprovements(Version)} is at least 2.3.24, then it's interpreted as a
-     *       custom number format. The format of such string is <code>"@<i>name</i>"</code> or
-     *       <code>"@<i>name</i> <i>parameters</i>"</code>, where <code><i>name</i></code> is the key in the
-     *       {@link Map} set by {@link #setCustomNumberFormats(Map)}, and <code><i>parameters</i></code> is parsed by
-     *       the custom {@link TemplateNumberFormat}.
+     *   <li>If the string starts with {@code @} character then it's interpreted as a custom number format, but only if
+     *       either {@link Configuration#getIncompatibleImprovements()} is at least 2.3.24, or there's any custom
+     *       formats defined (even if custom date/time/dateTime format). The format of a such string is
+     *       <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomNumberFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom {@link TemplateNumberFormat}.
      *   </li>
      * </ul>
      * 
@@ -771,7 +771,11 @@ public class Configurable {
     
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setNumberFormat(String)
-     * number_format} setting with values starting with <code>@<i>name</i></code>.
+     * number_format} setting with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} will have special meaning in number/date/time/datetime format strings, even if
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less than 2.3.24 (starting with
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24 {@code @} always has special
+     * meaning).
      * 
      * @param customNumberFormats
      *            Can't be {@code null}.
@@ -827,6 +831,17 @@ public class Configurable {
     }
     
     /**
+     * Tells if this configurable object or its parent defines any custom formats.
+     * 
+     * @since 2.3.24
+     */
+    public boolean hasCustomFormats() {
+        return customNumberFormats != null && !customNumberFormats.isEmpty()
+                || customDateFormats != null && !customDateFormats.isEmpty()
+                || getParent() != null && getParent().hasCustomFormats(); 
+    }
+    
+    /**
      * The string value for the boolean {@code true} and {@code false} values, intended for human audience (not for a
      * computer language), separated with comma. For example, {@code "yes,no"}. Note that white-space is significant,
      * so {@code "yes, no"} is WRONG (unless you want that leading space before "no").
@@ -1082,12 +1097,12 @@ public class Configurable {
      *       them with {@code _}, like {@code "short_medium"}. ({@code "medium"} means
      *       {@code "medium_medium"} for date-time values.)
      *       
-     *   <li><p>Anything that starts with {@code "@"}, but only if
-     *       {@link Configuration#setIncompatibleImprovements(Version)} is at least 2.3.24, is interpreted as a custom
-     *       date/time/dateTime format. The format of such string is <code>"@<i>name</i>"</code> or
-     *       <code>"@<i>name</i> <i>parameters</i>"</code>, where <code><i>name</i></code> is the key in the
-     *       {@link Map} set by {@link #setCustomDateFormats(Map)}, and <code><i>parameters</i></code> is parsed by the
-     *       custom number format.
+     *   <li><p>Anything that starts with {@code "@"} is interpreted as a custom
+     *       date/time/dateTime format, but only if either {@link Configuration#getIncompatibleImprovements()}
+     *       is at least 2.3.24, or there's any custom formats defined (even if custom number format). The format of
+     *       such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomDateFormats(Map)}, and
+     *       <code><i>parameters</i></code> is parsed by the custom number format.
      *       
      * </ul> 
      * 
@@ -1127,7 +1142,11 @@ public class Configurable {
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setDateTimeFormat(String)
      * date_format}, {@link #setDateTimeFormat(String) time_format}, and {@link #setDateTimeFormat(String)
-     * datetime_format} settings with values starting with <code>@<i>name</i></code>.
+     * datetime_format} settings with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
+     * formats here, an initial {@code @} will have special meaning in number/date/time/datetime format strings, even if
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less than 2.3.24 (starting with
+     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24 {@code @} always has special
+     * meaning).
      *
      * @param customDateFormats
      *            Can't be {@code null}.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/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 da980c7..96e31fd 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1236,7 +1236,7 @@ public final class Environment extends Configurable {
         if (formatStringLen > 1
                 && formatString.charAt(0) == '@'
                 && formatString.charAt(1) != '@'
-                && isIcI2324OrLater()) {
+                && (isIcI2324OrLater() || hasCustomFormats())) {
             final String name;
             final String params;
             {
@@ -1262,7 +1262,7 @@ public final class Environment extends Configurable {
             if (formatStringLen > 1
                     && formatString.charAt(0) == '@'
                     && formatString.charAt(1) == '@'
-                    && isIcI2324OrLater()) {
+                    && (isIcI2324OrLater() || hasCustomFormats())) {
                 // Unescape @ escaped as @@
                 formatString = formatString.substring(1);
             }
@@ -1743,7 +1743,7 @@ public final class Environment extends Configurable {
         } else if (firstChar == '@'
                 && formatStringLen > 1
                 && formatString.charAt(1) != '@'
-                && isIcI2324OrLater()) {
+                && (isIcI2324OrLater() || hasCustomFormats())) {
             final String name;
             {
                 int endIdx;
@@ -1767,7 +1767,7 @@ public final class Environment extends Configurable {
             if (firstChar == '@'
                     && formatStringLen > 1
                     && formatString.charAt(1) == '@'
-                    && isIcI2324OrLater()) {
+                    && (isIcI2324OrLater() || hasCustomFormats())) {
                 // Unescape @ escaped as @@
                 unescapedFormatString = formatString.substring(1);
             } else {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/main/java/freemarker/template/Configuration.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/Configuration.java b/src/main/java/freemarker/template/Configuration.java
index 4f0b015..fe178dc 100644
--- a/src/main/java/freemarker/template/Configuration.java
+++ b/src/main/java/freemarker/template/Configuration.java
@@ -740,9 +740,21 @@ public class Configuration extends Configurable implements Cloneable, ParserConf
      *          These "file" extensions aren't case sensitive.
      *       </li>
      *       <li><p>
+     *          In number format and date format strings (like in the {@code number_format} setting, or in templates in
+     *          {@code n?string("0.##")}), an initial {@code '@'} has special meaning; they refer to a custom format
+     *          with the name given after the {@code @} (see: {@link #setCustomNumberFormats(Map)},
+     *          {@link #setCustomDateFormats(Map)}, {@link #setNumberFormat(String)}, and {@link #setDateTimeFormat}).
+     *          If the custom format doesn't exist, that will be an error. To have a literal {@code @} as the first
+     *          character in the output, it has to be written as {@code @@}. Again, all this only applies to the very
+     *          first character of the format string, so {@code @} characters elsewhere must not be doubled. Also, if
+     *          there are any custom formats defined, initial {@code '@'} will have the new meaning regardless of
+     *          the value of the {@code incompatible_improvements} setting. So you don't need to set the
+     *          {@code incompatible_improvements} only to use custom formats. 
+     *       </li>
+     *       <li><p>
      *          Expressions inside interpolations that were inside <em>string literal expressions</em>
      *          (not <code>${...}</code>-s in general), like in <code>&lt;#assign s="Hello ${name}!"&gt;</code>, has
-     *          always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect).
+     *          always used {@code incompatbileImprovement}-s 0 (2.3.0 in effect). Now it's fixed.
      *       </li>
      *     </ul>
      *   </li>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 17417ec..af492e4 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -9155,13 +9155,10 @@ cfg.setTemplateConfigurers(
         formats can be registered with the
         <literal>custom_number_formats</literal> and
         <literal>custom_date_formats</literal> configuration settings. After
-        that, if you <link
-        linkend="pgui_config_incompatible_improvements_how_to_set">set the
-        <literal>incompatible_improvements</literal> setting</link> to 2.3.24
-        or higher, anywhere where you can specify formats with a
+        that, anywhere where you can specify formats with a
         <literal>String</literal>, now you can refer to your custom format as
         <literal>"@<replaceable>name</replaceable>"</literal>. So for example,
-        if you have registered a your number format implementation with name
+        if you have registered your number format implementation with name
         <literal>"smart"</literal>, then you could set the
         <literal>number_format</literal> setting
         (<literal>Configurable.setNumberFormat(String)</literal>) to
@@ -12335,8 +12332,8 @@ Green Mouse</programlisting>
           such returns exactly the same Java object that the date parser
           (<literal>freemarker.core.TemplateDateFormat</literal>
           implementation) returns, while <literal>?date</literal> without the
-          <literal>()</literal> returns a tricky wrapper that's a date and a
-          method and hash on the same time.</para>
+          <literal>()</literal> returns a tricky wrapper value that's a date
+          and a method and hash on the same time.</para>
         </section>
 
         <section xml:id="ref_builtin_ends_with">
@@ -20575,16 +20572,18 @@ ${"'{}"}
                 </listitem>
 
                 <listitem>
-                  <para>Values starting with <literal>@</literal>, if <link
+                  <para>Values starting with <literal>@</literal> refer to a
+                  <link linkend="pgui_config_custom_formats">custom
+                  format</link>, but only if either <link
                   linkend="pgui_config_incompatible_improvements_how_to_set">the
                   <literal>incompatible_improvements</literal> setting</link>
-                  is at least 2.3.24, refers to a <link
-                  linkend="pgui_config_custom_formats">custom format</link>.
-                  For example, <literal>"@price"</literal> refers to the
-                  custom format registered with the <literal>"price"</literal>
-                  name. The custom format name is possibly followed by space
-                  or <literal>_</literal> and then format parameters, whose
-                  interpretation depends on the custom format.</para>
+                  is at least 2.3.24, or there are some custom formats
+                  defined. For example, <literal>"@price"</literal> refers to
+                  the custom format registered with the
+                  <literal>"price"</literal> name. The custom format name is
+                  possibly followed by space or <literal>_</literal> and then
+                  format parameters, whose interpretation depends on the
+                  custom format.</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -20833,16 +20832,20 @@ ${"'{}"}
                 </listitem>
 
                 <listitem>
-                  <para>Values starting with <literal>@</literal>, if <link
+                  <para>Values starting with <literal>@</literal> refer to a
+                  <link linkend="pgui_config_custom_formats">custom
+                  format</link>, like <literal>"@departure"</literal> refers
+                  to the custom format registered with the
+                  <literal>"departure"</literal> name. The format name is
+                  possibly followed by space or <literal>_</literal> and then
+                  format parameters, whose interpretation depends on the
+                  custom format. For backward compatibility, the initial
+                  <literal>@</literal> only has this new meaning if either
+                  <link
                   linkend="pgui_config_incompatible_improvements_how_to_set">the
                   <literal>incompatible_improvements</literal> setting</link>
-                  is at least 2.3.24, refer to a <link
-                  linkend="pgui_config_custom_formats">custom format</link>,
-                  like <literal>"@departure"</literal> refers to the custom
-                  format registered with the <literal>"departure"</literal>
-                  name. The format name is possibly followed by space or
-                  <literal>_</literal> and then format parameters, whose
-                  interpretation depends on the custom format.</para>
+                  is at least 2.3.24, or there's any custom formats
+                  defined.</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -25751,14 +25754,12 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               <literal>${n?string.@foo_params}</literal>,
               <literal>&lt;#setting number_format='@foo params'&gt;</literal>,
               or <literal>${n?string.@foo}</literal>,
-              <literal>${n?string.@foo_params}</literal>. This means that
-              <replaceable>if</replaceable> custom formats are allowed in the
-              configuration (by <link
-              linkend="pgui_config_incompatible_improvements_how_to_set">setting
-              <literal>incompatible_improvements</literal></link> to 2.3.24),
-              an <replaceable>initial</replaceable> <literal>@</literal> in
-              format strings is now reserved, and need to be escaped as
-              <literal>@@</literal> if it has to be there literally.</para>
+              <literal>${n?string.@foo_params}</literal>. For backward
+              compatibility, the initial <literal>@</literal> only has this
+              special meaning if you have any custom formats or <link
+              linkend="pgui_config_incompatible_improvements">the
+              <literal>incompatible_improvements</literal> setting</link> is
+              2.3.24 or higher.</para>
             </listitem>
 
             <listitem>
@@ -26150,6 +26151,15 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>In number/date/time/datetime format strings, initial
+              <literal>@</literal> can be used to refer to custom formats even
+              if <literal>incompatible_improvements</literal> is less than
+              2.3.24, as far as there's any custom format defined. As custom
+              formats is also a 2.3.24 feature, it's backward compatible this
+              way.</para>
+            </listitem>
+
+            <listitem>
               <para><literal>?date</literal>, <literal>?time</literal> and
               <literal>?datetime</literal> now can be called as 0 argument
               method, like <literal>?date()</literal>, etc., which returns the

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/test/java/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/DateFormatTest.java b/src/test/java/freemarker/core/DateFormatTest.java
index af57f77..741e68b 100644
--- a/src/test/java/freemarker/core/DateFormatTest.java
+++ b/src/test/java/freemarker/core/DateFormatTest.java
@@ -24,6 +24,7 @@ import static org.junit.Assert.*;
 import java.io.IOException;
 import java.sql.Time;
 import java.sql.Timestamp;
+import java.util.Collections;
 import java.util.Date;
 import java.util.Locale;
 import java.util.TimeZone;
@@ -196,16 +197,31 @@ public class DateFormatTest extends TemplateTest {
     public void testIcIAndEscaping() throws Exception {
         Configuration cfg = getConfiguration();
         addToDataModel("d", new SimpleDate(new Date(12345678L), TemplateDateModel.DATETIME));
-        cfg.setDateTimeFormat("@@yyyy");
-        assertOutput("${d}", "@1970");
-        cfg.setDateTimeFormat("@epoch");
-        assertOutput("${d}", "12345678");
+        
+        testIcIAndEscapingWhenCustFormsAreAccepted(cfg);
         
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_23);
+        testIcIAndEscapingWhenCustFormsAreAccepted(cfg);
+        
+        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        
         cfg.setDateTimeFormat("@@yyyy");
         assertOutput("${d}", "@@1970");
         cfg.setDateTimeFormat("@epoch");
         assertErrorContains("${d}", "\"@epoch\"");
+        
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_24);
+        cfg.setDateTimeFormat("@@yyyy");
+        assertOutput("${d}", "@1970");
+        cfg.setDateTimeFormat("@epoch");
+        assertErrorContains("${d}", "custom", "\"epoch\"");
+    }
+
+    protected void testIcIAndEscapingWhenCustFormsAreAccepted(Configuration cfg) throws IOException, TemplateException {
+        cfg.setDateTimeFormat("@@yyyy");
+        assertOutput("${d}", "@1970");
+        cfg.setDateTimeFormat("@epoch");
+        assertOutput("${d}", "12345678");
     }
     
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/test/java/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index e2fc2b2..78b420f 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -22,6 +22,7 @@ import static org.hamcrest.Matchers.*;
 import static org.junit.Assert.*;
 
 import java.io.IOException;
+import java.util.Collections;
 import java.util.Locale;
 import java.util.Map;
 
@@ -208,16 +209,29 @@ public class NumberFormatTest extends TemplateTest {
     @Test
     public void testIcIAndEscaping() throws Exception {
         Configuration cfg = getConfiguration();
-        cfg.setNumberFormat("@@0");
-        assertOutput("${10}", "@10");
-        cfg.setNumberFormat("@hex");
-        assertOutput("${10}", "a");
+        testIcIAndEscapingWhenCustFormsAccepted(cfg);
         
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_23);
+        testIcIAndEscapingWhenCustFormsAccepted(cfg);
+        
+        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
         cfg.setNumberFormat("@@0");
         assertOutput("${10}", "@@10");
         cfg.setNumberFormat("@hex");
         assertOutput("${10}", "@hex10");
+        
+        cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_24);
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@hex");
+        assertErrorContains("${10}", "custom", "\"hex\"");
+    }
+
+    protected void testIcIAndEscapingWhenCustFormsAccepted(Configuration cfg) throws IOException, TemplateException {
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@hex");
+        assertOutput("${10}", "a");
     }
 
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/d5bfedba/src/test/java/freemarker/template/ConfigurationTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/template/ConfigurationTest.java b/src/test/java/freemarker/template/ConfigurationTest.java
index 6d5964f..b7de38e 100644
--- a/src/test/java/freemarker/template/ConfigurationTest.java
+++ b/src/test/java/freemarker/template/ConfigurationTest.java
@@ -1450,6 +1450,71 @@ public class ConfigurationTest extends TestCase {
         }
     }
     
+    @Test
+    public void testHasCustomFormats() throws IOException, TemplateException {
+        Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
+        Template t = new Template(null, "", cfg);
+        Environment env = t.createProcessingEnvironment(null, null);
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        env.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertFalse(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        t.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertFalse(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        
+        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        cfg.setCustomDateFormats(Collections.singletonMap("f", EpochMillisTemplateDateFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        
+        cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        t.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        env.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        // Same with number formats:
+        
+        env.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertFalse(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        t.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertFalse(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+        
+        cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
+        t.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
+        env.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
+        assertFalse(cfg.hasCustomFormats());
+        assertFalse(t.hasCustomFormats());
+        assertFalse(env.hasCustomFormats());
+        
+        cfg.setCustomNumberFormats(Collections.singletonMap("f", HexTemplateNumberFormatFactory.INSTANCE));
+        assertTrue(cfg.hasCustomFormats());
+        assertTrue(t.hasCustomFormats());
+        assertTrue(env.hasCustomFormats());
+    }
+    
     public void testNamingConventionSetSetting() throws TemplateException {
         Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);
 


[06/12] incubator-freemarker git commit: (Documentation fixes/improvements related to format strings and @)

Posted by dd...@apache.org.
(Documentation fixes/improvements related to format strings and @)


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

Branch: refs/heads/2.3
Commit: 9c2b9c871aeb80d5c3fbe595b65e79fc363af06f
Parents: 8e7c80a
Author: ddekany <dd...@apache.org>
Authored: Fri Sep 18 23:57:02 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Sep 18 23:57:02 2015 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java |  26 ++---
 src/manual/book.xml                             | 113 ++++++++++++-------
 2 files changed, 85 insertions(+), 54 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9c2b9c87/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index 4ad0a2e..e7b9a99 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -726,10 +726,10 @@ public class Configurable {
      *       rounding mode, and {@code _} as the group separator. See more about "extended Java decimal format" in the
      *       FreeMarker Manual.
      *       </li>
-     *   <li>If the string starts with {@code @} character then it's interpreted as a custom number format, but only if
-     *       either {@link Configuration#getIncompatibleImprovements()} is at least 2.3.24, or there's any custom
-     *       formats defined (even if custom date/time/dateTime format). The format of a such string is
-     *       <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
+     *   <li>If the string starts with {@code @} character followed by a letter then it's interpreted as a custom number
+     *       format, but only if either {@link Configuration#getIncompatibleImprovements()} is at least 2.3.24, or
+     *       there's any custom formats defined (even if custom date/time/dateTime format). The format of a such string
+     *       is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
      *       <code><i>name</i></code> is the key in the {@link Map} set by {@link #setCustomNumberFormats(Map)}, and
      *       <code><i>parameters</i></code> is parsed by the custom {@link TemplateNumberFormat}.
      *   </li>
@@ -772,10 +772,10 @@ public class Configurable {
     /**
      * Associates names with formatter factories, which then can be referred by the {@link #setNumberFormat(String)
      * number_format} setting with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
-     * formats here, an initial {@code @} will have special meaning in number/date/time/datetime format strings, even if
-     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less than 2.3.24 (starting with
-     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24 {@code @} always has special
-     * meaning).
+     * formats here, an initial {@code @} followed by a letter will have special meaning in number/date/time/datetime
+     * format strings, even if {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less
+     * than 2.3.24 (starting with {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
+     * {@code @} always has special meaning).
      * 
      * @param customNumberFormats
      *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
@@ -1102,7 +1102,7 @@ public class Configurable {
      *       them with {@code _}, like {@code "short_medium"}. ({@code "medium"} means
      *       {@code "medium_medium"} for date-time values.)
      *       
-     *   <li><p>Anything that starts with {@code "@"} is interpreted as a custom
+     *   <li><p>Anything that starts with {@code "@"} followed by a letter is interpreted as a custom
      *       date/time/dateTime format, but only if either {@link Configuration#getIncompatibleImprovements()}
      *       is at least 2.3.24, or there's any custom formats defined (even if custom number format). The format of
      *       such string is <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where
@@ -1148,10 +1148,10 @@ public class Configurable {
      * Associates names with formatter factories, which then can be referred by the {@link #setDateTimeFormat(String)
      * date_format}, {@link #setDateTimeFormat(String) time_format}, and {@link #setDateTimeFormat(String)
      * datetime_format} settings with values starting with <code>@<i>name</i></code>. Beware, if you specify any custom
-     * formats here, an initial {@code @} will have special meaning in number/date/time/datetime format strings, even if
-     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less than 2.3.24 (starting with
-     * {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24 {@code @} always has special
-     * meaning).
+     * formats here, an initial {@code @} followed by a letter will have special meaning in number/date/time/datetime
+     * format strings, even if {@link Configuration#getIncompatibleImprovements() incompatible_improvements} is less
+     * than 2.3.24 (starting with {@link Configuration#getIncompatibleImprovements() incompatible_improvements} 2.3.24
+     * {@code @} always has special meaning).
      *
      * @param customDateFormats
      *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9c2b9c87/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index 0506f18..fe7ab96 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -12314,7 +12314,10 @@ Green Mouse</programlisting>
 &lt;#-- Parsing with SimpleDateFormat patterns: --&gt;
 &lt;#assign someDate = "10/25/1995"?date("MM/dd/yyyy")&gt;
 &lt;#assign someTime = "15:05:30"?time("HH:mm:ss")&gt;
-&lt;#assign someDatetime = "1995-10-25 03:05 PM"?datetime("yyyy-MM-dd hh:mm a")&gt;</programlisting>
+&lt;#assign someDatetime = "1995-10-25 03:05 PM"?datetime("yyyy-MM-dd hh:mm a")&gt;
+
+&lt;#-- Parsing with custom date formats: --&gt;
+&lt;#assign someDatetime = "October/25/1995 03:05 PM"?datetime.@worklog&gt;</programlisting>
 
           <para>To prevent misunderstandings, the left-hand value need not be
           a string literal. For example, when you read data from XML DOM (from
@@ -14468,11 +14471,25 @@ $42.00
 
           <para>since the default number format was set to "currency".</para>
 
-          <para>Beside the three predefined formats, you can use arbitrary
-          number format patterns written in <link
+          <para>You can also refer to named custom formats that were defined
+          when configuring FreeMarker (programmers <link
+          linkend="pgui_config_custom_formats">see more here</link>),
+          like:</para>
+
+          <programlisting>${x?strong.@price}
+${x?strong.@weight}</programlisting>
+
+          <para>where the custom format names were <quote>price</quote> and
+          <quote>weight</quote>. Note that hence the templates can just refer
+          to the application-domain meaning, and the exact format can be
+          specified outside the templates, on a single central place.</para>
+
+          <para>Beside named formats, you can specify number format patterns
+          directly, using the <link
           xlink:href="http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html">Java
           decimal number format syntax</link> (with some FreeMarker-specific
-          extensions; see later):</para>
+          extensions; <link linkend="topic.extendedJavaDecimalFormat">see
+          later</link>):</para>
 
           <programlisting role="template">&lt;#assign x = 1.234&gt;
 ${x?string["0"]}
@@ -15166,7 +15183,9 @@ The usual variations are supported:
           <literal>java.sql.Timestamp</literal> or
           <literal>java.util.Date</literal> then this:</para>
 
-          <programlisting role="template">${openingTime?string.short}
+          <programlisting role="template">&lt;#-- Predefined format names: --&gt;
+
+${openingTime?string.short}
 ${openingTime?string.medium}
 ${openingTime?string.long}
 ${openingTime?string.full}
@@ -15188,18 +15207,19 @@ ${lastUpdated?string.medium_short} &lt;#-- medium date, short time --&gt;
 ${lastUpdated?string.xs}
 ${lastUpdated?string.iso}
 
+&lt;#-- <link linkend="pgui_config_custom_formats">Programmer-defined formats</link> (starts with @ + letter): --&gt;
+${lastUpdated?string.@lastMod}
+
+&lt;#-- More advanced ISO 8601-related formats: --&gt;
+${lastUpdated?string.iso_m_u}
+${lastUpdated?string.xs_ms_nz}
+
 &lt;#-- SimpleDateFormat patterns: --&gt;
 ${lastUpdated?string["dd.MM.yyyy, HH:mm"]}
 ${lastUpdated?string["EEEE, MMMM dd, yyyy, hh:mm a '('zzz')'"]}
 ${lastUpdated?string["EEE, MMM d, ''yy"]}
 ${lastUpdated?string.yyyy} &lt;#-- Same as ${lastUpdated?string["yyyy"]} --&gt;
-
-&lt;#-- Advanced ISO 8601-related formats: --&gt;
-${lastUpdated?string.iso_m_u}
-${lastUpdated?string.xs_ms_nz}
-
-&lt;#-- <link linkend="pgui_config_custom_formats">Programmer-defined formats</link> (start with @): --&gt;
-${lastUpdated?string.@file} (or however it was configured)</programlisting>
+</programlisting>
 
           <para>will print something like this:</para>
 
@@ -15225,15 +15245,20 @@ Feb 8, 2003 9:24 PM
 2007-02-20T13:45:09-08:00
 2007-02-20T13:45:09-08:00
 
-08.04.2003 21:24
-Tuesday, April 08, 2003, 09:24 PM (PDT)
-Tue, Apr 8, '03
-2003
+Apr/20/2007 13:45
 
 2007-02-20T21:45Z
 2007-02-20T13:45:09.000
 
-2007-02-20 13 (or however it was configured)</programlisting>
+08.04.2003 21:24
+Tuesday, April 08, 2003, 09:24 PM (PDT)
+Tue, Apr 8, '03
+2003</programlisting>
+
+          <para>Note that with custom formats like in
+          <literal>lastUpdated?string.@lastMod</literal> above, templates can
+          just refer to the application-domain meaning, and the exact format
+          can be specified outside the templates, on a central place.</para>
 
           <warning>
             <para>Unfortunately, because of the limitations of the Java
@@ -20572,18 +20597,22 @@ ${"'{}"}
                 </listitem>
 
                 <listitem>
-                  <para>Values starting with <literal>@</literal> refer to a
-                  <link linkend="pgui_config_custom_formats">custom
-                  format</link>, but only if either <link
+                  <para>Values starting with <literal>@</literal> that's also
+                  followed by a letter, refer to a <link
+                  linkend="pgui_config_custom_formats">custom format</link>.
+                  For example, <literal>"@price"</literal> refers to the
+                  custom format registered with the <literal>"price"</literal>
+                  name. The custom format name is possibly followed by space
+                  or <literal>_</literal> and then format parameters, whose
+                  interpretation depends on the custom format. For backward
+                  compatibility, the initial <literal>@</literal> only has
+                  this new meaning if either <link
                   linkend="pgui_config_incompatible_improvements_how_to_set">the
                   <literal>incompatible_improvements</literal> setting</link>
-                  is at least 2.3.24, or there are some custom formats
-                  defined. For example, <literal>"@price"</literal> refers to
-                  the custom format registered with the
-                  <literal>"price"</literal> name. The custom format name is
-                  possibly followed by space or <literal>_</literal> and then
-                  format parameters, whose interpretation depends on the
-                  custom format.</para>
+                  is at least 2.3.24, or there's any custom formats defined.
+                  When the initial <literal>@</literal> isn't followed by a
+                  letter (any UNICODE letter), it's never treated as a
+                  reference to a custom format.</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -20832,20 +20861,22 @@ ${"'{}"}
                 </listitem>
 
                 <listitem>
-                  <para>Values starting with <literal>@</literal> refer to a
-                  <link linkend="pgui_config_custom_formats">custom
-                  format</link>, like <literal>"@departure"</literal> refers
-                  to the custom format registered with the
-                  <literal>"departure"</literal> name. The format name is
-                  possibly followed by space or <literal>_</literal> and then
-                  format parameters, whose interpretation depends on the
-                  custom format. For backward compatibility, the initial
-                  <literal>@</literal> only has this new meaning if either
-                  <link
+                  <para>Values starting with <literal>@</literal> that's also
+                  followed by a letter, refer to a <link
+                  linkend="pgui_config_custom_formats">custom format</link>,
+                  like <literal>"@worklog"</literal> refers to the custom
+                  format registered with the <literal>"worklog"</literal>
+                  name. The format name is possibly followed by space or
+                  <literal>_</literal> and then format parameters, whose
+                  interpretation depends on the custom format. For backward
+                  compatibility, the initial <literal>@</literal> only has
+                  this new meaning if either <link
                   linkend="pgui_config_incompatible_improvements_how_to_set">the
                   <literal>incompatible_improvements</literal> setting</link>
-                  is at least 2.3.24, or there's any custom formats
-                  defined.</para>
+                  is at least 2.3.24, or there's any custom formats defined.
+                  When the initial <literal>@</literal> isn't followed by a
+                  letter (any UNICODE letter), it's never treated as a
+                  reference to a custom format.</para>
                 </listitem>
               </itemizedlist>
             </listitem>
@@ -20930,8 +20961,8 @@ ${"'{}"}
             </listitem>
           </itemizedlist>
 
-          <para>Example: Assume that the initial locale of template is de_DE
-          (German). Then this:</para>
+          <para>Example: Assume that the initial locale of template is
+          <literal>de_DE</literal> (German). Then this:</para>
 
           <programlisting role="template">${1.2}
 &lt;#setting locale="en_US"&gt;


[03/12] incubator-freemarker git commit: Documented extended DecimalFormat. Some other improvements in formatting-related documentation.

Posted by dd...@apache.org.
Documented extended DecimalFormat. Some other improvements in formatting-related documentation.


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

Branch: refs/heads/2.3
Commit: ee7f23f7f55135bd06ace919b6b5db82c36e4b27
Parents: 7f591d1
Author: ddekany <dd...@apache.org>
Authored: Fri Sep 18 00:50:27 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Sep 18 00:50:57 2015 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java |  22 +-
 src/manual/book.xml                             | 311 +++++++++++++++++--
 2 files changed, 300 insertions(+), 33 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ee7f23f7/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index f303086..44e3fcb 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -714,19 +714,27 @@ public class Configurable {
     }
 
     /**
-     * Sets the default number format used to convert numbers to strings. Currently, this is either a
-     * {@link java.text.DecimalFormat} pattern (like {@code "0.##"}), or one of the following special values:
+     * Sets the default number format used to convert numbers to strings. Currently, this is one of these:
      * <ul>
      *   <li>{@code "number"}: The number format returned by {@link NumberFormat#getNumberInstance(Locale)}</li>
      *   <li>{@code "currency"}: The number format returned by {@link NumberFormat#getCurrencyInstance(Locale)}</li>
      *   <li>{@code "percent"}: The number format returned by {@link NumberFormat#getPercentInstance(Locale)}</li>
      *   <li>{@code "computer"}: The number format used by FTL's {@code c} built-in (like in {@code someNumber?c}).</li>
+     *   <li>{@link java.text.DecimalFormat} pattern (like {@code "0.##"}). This syntax has a FreeMarker-specific
+     *       extension, so that you can specify options like the rounding mode and the symbols used in this string. For
+     *       example, {@code ",000;; rnd=hu grp=_"} will format numbers like {@code ",000"} would, but with half-up
+     *       rounding mode, and {@code _} as the group separator. See more about "extended Java decimal format" in the
+     *       FreeMarker Manual.
+     *       </li>
+     *   <li>If the string starts with {@code @} character, and
+     *       {@link Configuration#setIncompatibleImprovements(Version)} is at least 2.3.24, then it's interpreted as a
+     *       custom number format. The format of such string is <code>"@<i>name</i>"</code> or
+     *       <code>"@<i>name</i> <i>parameters</i>"</code>, where <code><i>name</i></code> is the key in the
+     *       {@link Map} set by {@link #setCustomNumberFormats(Map)}, and <code><i>parameters</i></code> is parsed by
+     *       the custom {@link TemplateNumberFormat}.
+     *   </li>
      * </ul>
-     * Or, if {@link Configuration#setIncompatibleImprovements(Version)} is at least 2.3.24, and the string starts
-     * with {@code @} character, it's interpreted as a custom number format. The format of such string is
-     * <code>"@<i>name</i>"</code> or <code>"@<i>name</i> <i>parameters</i>"</code>, where <code><i>name</i></code>
-     * is the key in the {@link Map} set by {@link #setCustomNumberFormats(Map)}, and <code><i>parameters</i></code>
-     * is parsed by the custom number format.
+     * 
      *   
      * <p>Defaults to <tt>"number"</tt>.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/ee7f23f7/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index b1135ff..17417ec 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -12335,7 +12335,7 @@ Green Mouse</programlisting>
           such returns exactly the same Java object that the date parser
           (<literal>freemarker.core.TemplateDateFormat</literal>
           implementation) returns, while <literal>?date</literal> without the
-          <literal>()</literal> returns tricky wrapper that's a date and a
+          <literal>()</literal> returns a tricky wrapper that's a date and a
           method and hash on the same time.</para>
         </section>
 
@@ -14473,8 +14473,9 @@ $42.00
 
           <para>Beside the three predefined formats, you can use arbitrary
           number format patterns written in <link
-          xlink:href="http://java.sun.com/j2se/1.4/docs/api/java/text/DecimalFormat.html">Java
-          decimal number format syntax</link>:</para>
+          xlink:href="http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html">Java
+          decimal number format syntax</link> (with some FreeMarker-specific
+          extensions; see later):</para>
 
           <programlisting role="template">&lt;#assign x = 1.234&gt;
 ${x?string["0"]}
@@ -14494,8 +14495,6 @@ ${2.5?string["0"]} &lt;-- 2.5, rounded towards even neighbor
 
 ${12345?string["0.##E0"]}</programlisting>
 
-          <para>outputs this:</para>
-
           <programlisting role="output">1
 1.2
 1.23
@@ -14526,13 +14525,18 @@ ${12345?string["0.##E0"]}</programlisting>
           <literal>x?string("0.#")</literal>, which does exactly the same as
           <literal>x?string["0.#"]</literal>.</para>
 
-          <para>Following the financial and statistics practice, the rounding
-          goes according the so called half-even rule, which means rounding
-          towards the nearest ``neighbor'', unless both neighbors are
-          equidistant, in which case, it rounds towards the even neighbor.
-          This was visible in the above example if you look at the rounding of
-          1.5 and of 2.5, as both were rounded to 2, since 2 is even, but 1
-          and 3 are odds.</para>
+          <para>Following the financial and statistics practice, by default
+          the rounding goes according the so called half-even rule, which
+          means rounding towards the nearest <quote>neighbor</quote>, unless
+          both neighbors are equidistant, in which case, it rounds towards the
+          even neighbor. This was visible in the above example if you look at
+          the rounding of 1.5 and of 2.5, as both were rounded to 2, since 2
+          is even, but 1 and 3 are odds. The other popular rounding rule,
+          where we always round up when the neighbors are equidistant (and so
+          2.5 is rounded to 3) is called the half-up rule, and it can be
+          activated as <link
+          linkend="topic.extendedJavaDecimalFormat">described
+          later</link>.</para>
 
           <para>As it was shown for the predefined formats earlier, the
           default formatting of the numbers can be set in the template:</para>
@@ -14540,10 +14544,12 @@ ${12345?string["0.##E0"]}</programlisting>
           <programlisting role="template">&lt;#setting number_format="0.##"&gt;
 ${1.234}</programlisting>
 
-          <para>outputs this:</para>
-
           <programlisting role="output">1.23</programlisting>
 
+          <para>The default number format also can be specified outside the
+          templates with the FreeMarker API (like with
+          <literal>Configuration.setNumberFormat(String)</literal>).</para>
+
           <para>Note that as number formatting is locale sensitive, the locale
           setting also plays role in the formatting:</para>
 
@@ -14553,10 +14559,189 @@ US people write:     ${12345678}
 &lt;#setting locale="hu"&gt;
 German people write: ${12345678}</programlisting>
 
-          <para>outputs this:</para>
-
           <programlisting role="output">US people write:     12,345,678.00
 German people write: 12.345.678,00</programlisting>
+
+          <simplesect xml:id="topic.extendedJavaDecimalFormat">
+            <title>Extended Java decimal format </title>
+
+            <indexterm>
+              <primary>extended Java decimal format</primary>
+            </indexterm>
+
+            <para>FreeMarker extends the Java decimal format patterns with
+            extra options. These options are name-value pairs, specified after
+            two semicolons (<literal>;;</literal>) at the end of the format
+            string, or if you had a negative pattern (which is separated from
+            the normal patter with a semicolon, like in <literal>"0.0;minus
+            0.0"</literal>), the after only one semicolon. For example:</para>
+
+            <programlisting role="template">Standard decimal format: ${10002.5?string[",000"]}
+Extended decimal format: ${10002.5?string[",000<emphasis>;; rnd=hu grp=_</emphasis>"]}</programlisting>
+
+            <programlisting role="output">Standard decimal format: 10,002
+Extended decimal format: 10<emphasis>_</emphasis>00<emphasis>3</emphasis></programlisting>
+
+            <para>Above, in the extended decimal format, we have specified
+            half-up rounding mode (<literal>rnd=hu</literal>), and group
+            separator <literal>"_"</literal> (<literal>grp=_</literal>). The
+            table of all options:</para>
+
+            <informaltable border="1">
+              <thead>
+                <tr>
+                  <th>Name</th>
+
+                  <th>Meaning / value</th>
+                </tr>
+              </thead>
+
+              <tbody>
+                <tr>
+                  <td><literal>rnd</literal></td>
+
+                  <td>Rounding mode. The value is one of <literal>u</literal>
+                  for up, <literal>d</literal> for down, <literal>c</literal>
+                  for ceiling, <literal>f</literal> for floor,
+                  <literal>hu</literal> for half-up, <literal>hd</literal> for
+                  half-down, <literal>he</literal> for half-even, and
+                  <literal>un</literal> for unused. (See <link
+                  xlink:href="http://docs.oracle.com/javase/7/docs/api/java/math/RoundingMode.html">the
+                  <literal>java.math.RoundingMode</literal> API</link> for
+                  explanations.)</td>
+                </tr>
+
+                <tr>
+                  <td><literal>mul</literal></td>
+
+                  <td>Multiplier. The number will be shown after multiplied
+                  with this integer number.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>dec</literal></td>
+
+                  <td>Decimal separator character (like <literal>"."</literal>
+                  in <literal>3.14</literal>).</td>
+                </tr>
+
+                <tr>
+                  <td><literal>mdec</literal></td>
+
+                  <td>Monetary decimal separator character. This is used
+                  instead of <literal>dec</literal> when the pattern contains
+                  parts that make it a monetary format. (See the <link
+                  xlink:href="http://docs.oracle.com/javase/7/docs/api/java/text/DecimalFormat.html">Java
+                  decimal number format documentation</link> for more.)</td>
+                </tr>
+
+                <tr>
+                  <td><literal>grp</literal></td>
+
+                  <td>Grouping separator character. Note that grouping is
+                  turned on by using <literal>","</literal> in the patter, as
+                  shown in the earlier example. If it's not turned on, this
+                  option won't have visible effect.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>exp</literal></td>
+
+                  <td>Exponent separator string. Only has visible effect if
+                  the pattern specifies exponential form, like
+                  <literal>"0.##E0"</literal>.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>min</literal></td>
+
+                  <td>Minus sign character.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>inf</literal></td>
+
+                  <td>The string used to show infinity.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>nan</literal></td>
+
+                  <td>The string used to show not-a-number (NaN).</td>
+                </tr>
+
+                <tr>
+                  <td><literal>prc</literal></td>
+
+                  <td>Percent character.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>prm</literal></td>
+
+                  <td>Per-mill character.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>zero</literal></td>
+
+                  <td>Zero character. This modifies the other digits too, for
+                  example, if zero is <literal>A</literal>, then 1 will
+                  <literal>B</literal>, 2 will be <literal>C</literal>, and so
+                  on.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>curc</literal></td>
+
+                  <td>Currency ISO 4217 code. Only has effect when the pattern
+                  contains parts that make it a monetary format. It's an error
+                  to specify a code that's not a known ISO 4217 code in the
+                  Java installation.</td>
+                </tr>
+
+                <tr>
+                  <td><literal>curs</literal></td>
+
+                  <td>Currency symbol; shown where the localized currency name
+                  is present in the pattern. Overrides the symbol determined
+                  from <literal>curc</literal>.</td>
+                </tr>
+              </tbody>
+            </informaltable>
+
+            <para>Regarding the syntax of the options:</para>
+
+            <itemizedlist>
+              <listitem>
+                <para>The option name and value are separated by equals
+                character (<literal>=</literal>).</para>
+              </listitem>
+
+              <listitem>
+                <para>Options are separated by whitespace and/or optional
+                comma (<literal>,</literal>)</para>
+              </listitem>
+
+              <listitem>
+                <para>The option value can be quoted with apostrophe
+                (<literal>'</literal>) or normal quotation mark
+                (<literal>"</literal>) , like <literal>exp='*10^'</literal> or
+                <literal>exp="*10^"</literal>. If the value itself has to
+                contain the character used for quotation, then it has to be
+                entered twice (like <literal>inf='It''s infinite'</literal>,
+                but you could also write <literal>inf="It's
+                infinite"</literal>). Backslash has no special meaning.</para>
+              </listitem>
+
+              <listitem>
+                <para>Non-string values must not be quoted. Strings only has
+                to be quoted if they contain punctuation or whitespace, or any
+                other non-letter non-digit non-<literal>"_"</literal>
+                non-<literal>"$"</literal> characters.</para>
+              </listitem>
+            </itemizedlist>
+          </simplesect>
         </section>
 
         <section xml:id="ref_builtin_upper_abc">
@@ -20362,16 +20547,46 @@ ${"'{}"}
                   <secondary>number</secondary>
                 </indexterm><literal>number_format</literal>: The number
               format that is used to convert numbers to strings when no
-              explicit format is specified. Can be one of predefined values
-              <literal>number</literal> (the default),
-              <literal>computer</literal>, <literal>currency</literal>, or
-              <literal>percent</literal>. Additionally, arbitrary format
-              pattern written in <link
-              xlink:href="http://java.sun.com/j2se/1.4/docs/api/java/text/DecimalFormat.html">Java
-              decimal number format syntax</link> can also be specified. More
-              information about format patterns:<link
-              linkend="ref_builtin_string_for_number"><literal>string</literal>
-              built-in</link>.</para>
+              explicit format is specified. Can be one of the
+              following:</para>
+
+              <itemizedlist>
+                <listitem>
+                  <para>Predefined values defined by the Java platform:
+                  <literal>number</literal> (the default),
+                  <literal>currency</literal>, or
+                  <literal>percent</literal></para>
+                </listitem>
+
+                <listitem>
+                  <para><literal>computer</literal>, which formats like <link
+                  linkend="ref_builtin_c">the <literal>c</literal>
+                  built-in</link></para>
+                </listitem>
+
+                <listitem>
+                  <para>Format pattern written in <link
+                  xlink:href="http://java.sun.com/j2se/1.4/docs/api/java/text/DecimalFormat.html">Java
+                  decimal number format syntax</link>, for example
+                  <literal>0.###</literal>. FreeMarker <link
+                  linkend="topic.extendedJavaDecimalFormat">extends this
+                  format</link> to allow specifying rounding mode, symbols
+                  used, etc.</para>
+                </listitem>
+
+                <listitem>
+                  <para>Values starting with <literal>@</literal>, if <link
+                  linkend="pgui_config_incompatible_improvements_how_to_set">the
+                  <literal>incompatible_improvements</literal> setting</link>
+                  is at least 2.3.24, refers to a <link
+                  linkend="pgui_config_custom_formats">custom format</link>.
+                  For example, <literal>"@price"</literal> refers to the
+                  custom format registered with the <literal>"price"</literal>
+                  name. The custom format name is possibly followed by space
+                  or <literal>_</literal> and then format parameters, whose
+                  interpretation depends on the custom format.</para>
+                </listitem>
+              </itemizedlist>
             </listitem>
 
             <listitem>
@@ -20616,6 +20831,19 @@ ${"'{}"}
                   <literal>"medium_medium"</literal> for date-time
                   values.)</para>
                 </listitem>
+
+                <listitem>
+                  <para>Values starting with <literal>@</literal>, if <link
+                  linkend="pgui_config_incompatible_improvements_how_to_set">the
+                  <literal>incompatible_improvements</literal> setting</link>
+                  is at least 2.3.24, refer to a <link
+                  linkend="pgui_config_custom_formats">custom format</link>,
+                  like <literal>"@departure"</literal> refers to the custom
+                  format registered with the <literal>"departure"</literal>
+                  name. The format name is possibly followed by space or
+                  <literal>_</literal> and then format parameters, whose
+                  interpretation depends on the custom format.</para>
+                </listitem>
               </itemizedlist>
             </listitem>
 
@@ -25534,6 +25762,16 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>Everywhere where Java <literal>DecimalFormat</literal>
+              patterns are used (like in <literal>?string('0.##')</literal> or
+              <literal>&lt;#setting number_format="0.##"&gt;</literal>), now
+              it's possible to specify options like rounding mode or the
+              symbols used, with a FreeMarker-specific <link
+              linkend="topic.extendedJavaDecimalFormat">extension to the
+              pattern syntax</link>.</para>
+            </listitem>
+
+            <listitem>
               <para>Added new special variable:
               <literal>.incompatible_improvements</literal>, which returns the
               <link
@@ -25730,6 +25968,16 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>In the <literal>number_format</literal> configuration
+              setting, when it holds a Java <literal>DecimalFormat</literal>
+              pattern (like <literal>"0.##"</literal>), it's now possible to
+              specify options like rounding mode or the symbols used, with a
+              FreeMarker-specific <link
+              linkend="topic.extendedJavaDecimalFormat">extension to the
+              pattern syntax</link>.</para>
+            </listitem>
+
+            <listitem>
               <para>Added
               <literal>freemarker.cache.ByteArrayTemplateLoader</literal>,
               which is similar to <literal>StringTemplateLoader</literal>, but
@@ -25891,6 +26139,17 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>Everywhere where Java <literal>DecimalFormat</literal>
+              patterns are used (like in the <literal>number_format</literal>
+              configuration setting, or in
+              <literal>?string('0.##')</literal>), now it's possible to
+              specify options like rounding mode or the symbols used, with a
+              FreeMarker-specific <link
+              linkend="topic.extendedJavaDecimalFormat">extension to the
+              pattern syntax</link>.</para>
+            </listitem>
+
+            <listitem>
               <para><literal>?date</literal>, <literal>?time</literal> and
               <literal>?datetime</literal> now can be called as 0 argument
               method, like <literal>?date()</literal>, etc., which returns the


[05/12] incubator-freemarker git commit: @@ can't be used to escape @ at the beginning of format strings anymore, because it was confusing on Android, where DecimalFormat supports patterns like "@@@@@@". If a pattern has to output a literal @ as the firs

Posted by dd...@apache.org.
@@ can't be used to escape @ at the beginning of format strings anymore, because it was confusing on Android, where DecimalFormat supports patterns like "@@@@@@". If a pattern has to output a literal @ as the first character, you can simply use quoting as defined by DecimalFormat and SimpleDateFormat (for example, "'@'0.##").


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

Branch: refs/heads/2.3
Commit: 8e7c80a439273ea81a660611b4fa4a807b6c4e6f
Parents: d5bfedb
Author: ddekany <dd...@apache.org>
Authored: Fri Sep 18 22:20:21 2015 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Sep 18 22:21:31 2015 +0200

----------------------------------------------------------------------
 src/main/java/freemarker/core/Configurable.java | 14 +++++++---
 src/main/java/freemarker/core/Environment.java  | 28 ++++---------------
 src/manual/book.xml                             | 29 ++++++++++++++------
 .../java/freemarker/core/DateFormatTest.java    | 18 ++++++++----
 .../java/freemarker/core/NumberFormatTest.java  | 18 ++++++++----
 5 files changed, 60 insertions(+), 47 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/8e7c80a4/src/main/java/freemarker/core/Configurable.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/Configurable.java b/src/main/java/freemarker/core/Configurable.java
index bdc76ae..4ad0a2e 100644
--- a/src/main/java/freemarker/core/Configurable.java
+++ b/src/main/java/freemarker/core/Configurable.java
@@ -778,7 +778,8 @@ public class Configurable {
      * meaning).
      * 
      * @param customNumberFormats
-     *            Can't be {@code null}.
+     *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
+     *            letters and digits.
      * 
      * @since 2.3.24
      */
@@ -793,12 +794,16 @@ public class Configurable {
             if (name.length() == 0) {
                 throw new IllegalArgumentException("Format names can't be 0 length");
             }
-            if (name.charAt(0) == '@') {
+            char firstChar = name.charAt(0);
+            if (firstChar == '@') {
                 throw new IllegalArgumentException(
                         "Format names can't start with '@'. '@' is only used when referring to them from format "
                         + "strings. In: " + name);
             }
-            for (int i = 0; i < name.length(); i++) {
+            if (!Character.isLetter(firstChar)) {
+                throw new IllegalArgumentException("Format name must start with letter: " + name);
+            }
+            for (int i = 1; i < name.length(); i++) {
                 // Note that we deliberately don't allow "_" here.
                 if (!Character.isLetterOrDigit(name.charAt(i))) {
                     throw new IllegalArgumentException("Format name can only contain letters and digits: " + name);
@@ -1149,7 +1154,8 @@ public class Configurable {
      * meaning).
      *
      * @param customDateFormats
-     *            Can't be {@code null}.
+     *            Can't be {@code null}. The name must start with an UNICODE letter, and can only contain UNICODE
+     *            letters and digits.
      * 
      * @since 2.3.24
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/8e7c80a4/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 96e31fd..1e6aa73 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -1235,8 +1235,8 @@ public final class Environment extends Configurable {
         int formatStringLen = formatString.length();
         if (formatStringLen > 1
                 && formatString.charAt(0) == '@'
-                && formatString.charAt(1) != '@'
-                && (isIcI2324OrLater() || hasCustomFormats())) {
+                && (isIcI2324OrLater() || hasCustomFormats())
+                && Character.isLetter(formatString.charAt(1))) {
             final String name;
             final String params;
             {
@@ -1259,13 +1259,6 @@ public final class Environment extends Configurable {
 
             return formatFactory.get(params, locale, this);
         } else {
-            if (formatStringLen > 1
-                    && formatString.charAt(0) == '@'
-                    && formatString.charAt(1) == '@'
-                    && (isIcI2324OrLater() || hasCustomFormats())) {
-                // Unescape @ escaped as @@
-                formatString = formatString.substring(1);
-            }
             return JavaTemplateNumberFormatFactory.INSTANCE.get(formatString, locale, this);
         }
     }
@@ -1742,8 +1735,8 @@ public final class Environment extends Configurable {
             formatParams = formatString; // for speed, we don't remove the prefix
         } else if (firstChar == '@'
                 && formatStringLen > 1
-                && formatString.charAt(1) != '@'
-                && (isIcI2324OrLater() || hasCustomFormats())) {
+                && (isIcI2324OrLater() || hasCustomFormats())
+                && Character.isLetter(formatString.charAt(1))) {
             final String name;
             {
                 int endIdx;
@@ -1763,19 +1756,8 @@ public final class Environment extends Configurable {
                         "No custom date format was defined with name " + StringUtil.jQuote(name));
             }
         } else {
-            String unescapedFormatString;
-            if (firstChar == '@'
-                    && formatStringLen > 1
-                    && formatString.charAt(1) == '@'
-                    && (isIcI2324OrLater() || hasCustomFormats())) {
-                // Unescape @ escaped as @@
-                unescapedFormatString = formatString.substring(1);
-            } else {
-                unescapedFormatString = formatString;
-            }
-
+            formatParams = formatString;
             formatFactory = JavaTemplateDateFormatFactory.INSTANCE;
-            formatParams = unescapedFormatString;
         }
 
         return formatFactory.get(formatParams, dateType, locale, timeZone,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/8e7c80a4/src/manual/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/book.xml b/src/manual/book.xml
index af492e4..0506f18 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -25756,10 +25756,10 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
               or <literal>${n?string.@foo}</literal>,
               <literal>${n?string.@foo_params}</literal>. For backward
               compatibility, the initial <literal>@</literal> only has this
-              special meaning if you have any custom formats or <link
+              special meaning if either you have any custom formats or <link
               linkend="pgui_config_incompatible_improvements">the
               <literal>incompatible_improvements</literal> setting</link> is
-              2.3.24 or higher.</para>
+              at lest 2.3.24.</para>
             </listitem>
 
             <listitem>
@@ -25915,12 +25915,12 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
                   Like, you can do <literal>cfg.setNumberFormat("@foo
                   params")</literal>, or <literal>&lt;#setting
                   number_format='@foo params'&gt;</literal>, or
-                  <literal>${n?string.@foo_params}</literal>. Note that an
-                  <replaceable>initial</replaceable> <literal>@</literal> in
-                  format strings is reserved for this purpose with
-                  <literal>incompatible_improvements</literal> to 2.3.24, and
-                  need to be escaped as <literal>@@</literal> if it has to be
-                  there literally. Note that the
+                  <literal>${n?string.@foo_params}</literal>. For backward
+                  compatibility, the initial <literal>@</literal> only has
+                  this special meaning if either you have any custom formats
+                  or <link linkend="pgui_config_incompatible_improvements">the
+                  <literal>incompatible_improvements</literal> setting</link>
+                  is at least 2.3.24. Note that the
                   <literal>custom_number_formats</literal> and
                   <literal>custom_date_formats</literal> settings can be set
                   per-template (via the new
@@ -26160,6 +26160,19 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para><literal>@@</literal> can't be used to escape
+              <literal>@</literal> at the beginning of format strings anymore,
+              because it was confusing on Android, where
+              <literal>DecimalFormat</literal> supports patterns like
+              <literal>"@@@@@@"</literal>. If a pattern has to output a
+              literal <literal>@</literal> as the first character, you can
+              simply use quoting as defined by
+              <literal>DecimalFormat</literal> and
+              <literal>SimpleDateFormat</literal> (for example,
+              <literal>"'@'0.##"</literal>).</para>
+            </listitem>
+
+            <listitem>
               <para><literal>?date</literal>, <literal>?time</literal> and
               <literal>?datetime</literal> now can be called as 0 argument
               method, like <literal>?date()</literal>, etc., which returns the

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/8e7c80a4/src/test/java/freemarker/core/DateFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/DateFormatTest.java b/src/test/java/freemarker/core/DateFormatTest.java
index 741e68b..c21b83a 100644
--- a/src/test/java/freemarker/core/DateFormatTest.java
+++ b/src/test/java/freemarker/core/DateFormatTest.java
@@ -205,23 +205,29 @@ public class DateFormatTest extends TemplateTest {
         
         cfg.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>emptyMap());
         
-        cfg.setDateTimeFormat("@@yyyy");
-        assertOutput("${d}", "@@1970");
         cfg.setDateTimeFormat("@epoch");
         assertErrorContains("${d}", "\"@epoch\"");
+        cfg.setDateTimeFormat("'@'yyyy");
+        assertOutput("${d}", "@1970");
+        cfg.setDateTimeFormat("@@yyyy");
+        assertOutput("${d}", "@@1970");
         
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_24);
-        cfg.setDateTimeFormat("@@yyyy");
-        assertOutput("${d}", "@1970");
         cfg.setDateTimeFormat("@epoch");
         assertErrorContains("${d}", "custom", "\"epoch\"");
+        cfg.setDateTimeFormat("'@'yyyy");
+        assertOutput("${d}", "@1970");
+        cfg.setDateTimeFormat("@@yyyy");
+        assertOutput("${d}", "@@1970");
     }
 
     protected void testIcIAndEscapingWhenCustFormsAreAccepted(Configuration cfg) throws IOException, TemplateException {
-        cfg.setDateTimeFormat("@@yyyy");
-        assertOutput("${d}", "@1970");
         cfg.setDateTimeFormat("@epoch");
         assertOutput("${d}", "12345678");
+        cfg.setDateTimeFormat("'@'yyyy");
+        assertOutput("${d}", "@1970");
+        cfg.setDateTimeFormat("@@yyyy");
+        assertOutput("${d}", "@@1970");
     }
     
     @Test

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/8e7c80a4/src/test/java/freemarker/core/NumberFormatTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/NumberFormatTest.java b/src/test/java/freemarker/core/NumberFormatTest.java
index 78b420f..b30b72a 100644
--- a/src/test/java/freemarker/core/NumberFormatTest.java
+++ b/src/test/java/freemarker/core/NumberFormatTest.java
@@ -215,23 +215,29 @@ public class NumberFormatTest extends TemplateTest {
         testIcIAndEscapingWhenCustFormsAccepted(cfg);
         
         cfg.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>emptyMap());
-        cfg.setNumberFormat("@@0");
-        assertOutput("${10}", "@@10");
         cfg.setNumberFormat("@hex");
         assertOutput("${10}", "@hex10");
+        cfg.setNumberFormat("'@'0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@@10");
         
         cfg.setIncompatibleImprovements(Configuration.VERSION_2_3_24);
-        cfg.setNumberFormat("@@0");
-        assertOutput("${10}", "@10");
         cfg.setNumberFormat("@hex");
         assertErrorContains("${10}", "custom", "\"hex\"");
+        cfg.setNumberFormat("'@'0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@@10");
     }
 
     protected void testIcIAndEscapingWhenCustFormsAccepted(Configuration cfg) throws IOException, TemplateException {
-        cfg.setNumberFormat("@@0");
-        assertOutput("${10}", "@10");
         cfg.setNumberFormat("@hex");
         assertOutput("${10}", "a");
+        cfg.setNumberFormat("'@'0");
+        assertOutput("${10}", "@10");
+        cfg.setNumberFormat("@@0");
+        assertOutput("${10}", "@@10");
     }
 
     @Test