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 2017/05/14 10:52:53 UTC
[10/51] [partial] incubator-freemarker git commit: Migrated from Ant
to Gradle, and modularized the project. This is an incomplete migration;
there are some TODO-s in the build scripts, and release related tasks are
still missing. What works: Building th
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateFormatUtil.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateFormatUtil.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateFormatUtil.java
new file mode 100644
index 0000000..4029c78
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateFormatUtil.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat;
+
+import java.util.Date;
+
+import org.apache.freemarker.core._EvalUtil;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * Utility classes for implementing {@link TemplateValueFormat}-s.
+ *
+ * @since 2.3.24
+ */
+public final class TemplateFormatUtil {
+
+ private TemplateFormatUtil() {
+ // Not meant to be instantiated
+ }
+
+ public static void checkHasNoParameters(String params) throws InvalidFormatParametersException
+ {
+ if (params.length() != 0) {
+ throw new InvalidFormatParametersException(
+ "This number format doesn't support any parameters.");
+ }
+ }
+
+ /**
+ * Utility method to extract the {@link Number} from an {@link TemplateNumberModel}, and throws
+ * {@link TemplateModelException} with a standard error message if that's {@code null}. {@link TemplateNumberModel}
+ * that store {@code null} are in principle not allowed, and so are considered to be bugs in the
+ * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation.
+ */
+ public static Number getNonNullNumber(TemplateNumberModel numberModel)
+ throws TemplateModelException, UnformattableValueException {
+ Number number = numberModel.getAsNumber();
+ if (number == null) {
+ throw _EvalUtil.newModelHasStoredNullException(Number.class, numberModel, null);
+ }
+ return number;
+ }
+
+ /**
+ * Utility method to extract the {@link Date} from an {@link TemplateDateModel}, and throw
+ * {@link TemplateModelException} with a standard error message if that's {@code null}. {@link TemplateDateModel}
+ * that store {@code null} are in principle not allowed, and so are considered to be bugs in the
+ * {@link ObjectWrapper} or {@link TemplateNumberModel} implementation.
+ */
+ public static Date getNonNullDate(TemplateDateModel dateModel) throws TemplateModelException {
+ Date date = dateModel.getAsDate();
+ if (date == null) {
+ throw _EvalUtil.newModelHasStoredNullException(Date.class, dateModel, null);
+ }
+ return date;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java
new file mode 100644
index 0000000..54873d6
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormat.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat;
+
+import java.text.NumberFormat;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.model.TemplateNumberModel;
+
+/**
+ * Represents a number format; used in templates for formatting and parsing with that format. This is similar to Java's
+ * {@link NumberFormat}, but made to fit the requirements of FreeMarker. Also, it makes easier to define formats that
+ * can't be represented with Java's existing {@link NumberFormat} implementations.
+ *
+ * <p>
+ * Implementations need not be thread-safe if the {@link TemplateNumberFormatFactory} doesn't recycle them among
+ * different {@link Environment}-s. As far as FreeMarker's concerned, instances are bound to a single
+ * {@link Environment}, and {@link Environment}-s are thread-local objects.
+ *
+ * @since 2.3.24
+ */
+public abstract class TemplateNumberFormat extends TemplateValueFormat {
+
+ /**
+ * @param numberModel
+ * The number to format; not {@code null}. Most implementations will just work with the return value of
+ * {@link TemplateDateModel#getAsDate()}, but some may format differently depending on the properties of
+ * a custom {@link TemplateDateModel} implementation.
+ *
+ * @return The number as text, with no escaping (like no HTML escaping); can't be {@code null}.
+ *
+ * @throws TemplateValueFormatException
+ * If any problem occurs while parsing/getting the format. Notable subclass:
+ * {@link UnformattableValueException}.
+ * @throws TemplateModelException
+ * Exception thrown by the {@code dateModel} object when calling its methods.
+ */
+ public abstract String formatToPlainText(TemplateNumberModel numberModel)
+ throws TemplateValueFormatException, TemplateModelException;
+
+ /**
+ * Formats the model to markup instead of to plain text if the result markup will be more than just plain text
+ * escaped, otherwise falls back to formatting to plain text. If the markup result would be just the result of
+ * {@link #formatToPlainText(TemplateNumberModel)} escaped, it must return the {@link String} that
+ * {@link #formatToPlainText(TemplateNumberModel)} does.
+ *
+ * <p>
+ * The implementation in {@link TemplateNumberFormat} simply calls {@link #formatToPlainText(TemplateNumberModel)}.
+ *
+ * @return A {@link String} or a {@link TemplateMarkupOutputModel}; not {@code null}.
+ */
+ public Object format(TemplateNumberModel numberModel)
+ throws TemplateValueFormatException, TemplateModelException {
+ return formatToPlainText(numberModel);
+ }
+
+ /**
+ * Tells if this formatter should be re-created if the locale changes.
+ */
+ public abstract boolean isLocaleBound();
+
+ /**
+ * This method is reserved for future purposes; currently it always throws {@link ParsingNotSupportedException}. We
+ * don't yet support number parsing with {@link TemplateNumberFormat}-s, because currently FTL parses strings to
+ * number with the {@link ArithmeticEngine} ({@link TemplateNumberFormat} were only introduced in 2.3.24). If it
+ * will be support, it will be similar to {@link TemplateDateFormat#parse(String, int)}.
+ */
+ public final Object parse(String s) throws TemplateValueFormatException {
+ throw new ParsingNotSupportedException("Number formats currenly don't support parsing");
+ }
+
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java
new file mode 100644
index 0000000..a4cac22
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateNumberFormatFactory.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat;
+
+import java.util.Locale;
+
+import org.apache.freemarker.core.CustomStateKey;
+import org.apache.freemarker.core.MutableProcessingConfiguration;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.Environment;
+
+/**
+ * Factory for a certain kind of number formatting ({@link TemplateNumberFormat}). Usually a singleton (one-per-VM or
+ * one-per-{@link Configuration}), and so must be thread-safe.
+ *
+ * @see MutableProcessingConfiguration#setCustomNumberFormats(java.util.Map)
+ *
+ * @since 2.3.24
+ */
+public abstract class TemplateNumberFormatFactory extends TemplateValueFormatFactory {
+
+ /**
+ * Returns a formatter for the given parameters.
+ *
+ * <p>
+ * The returned formatter can be a new instance or a reused (cached) instance. Note that {@link Environment} itself
+ * caches the returned instances, though that cache is lost with the {@link Environment} (i.e., when the top-level
+ * template execution ends), also it might flushes lot of entries if the locale or time zone is changed during
+ * template execution. So caching on the factory level is still useful, unless creating the formatters is
+ * sufficiently cheap.
+ *
+ * @param params
+ * The string that further describes how the format should look. For example, when the
+ * {@link MutableProcessingConfiguration#getNumberFormat() numberFormat} is {@code "@fooBar 1, 2"}, then it will be
+ * {@code "1, 2"} (and {@code "@fooBar"} selects the factory). The format of this string is up to the
+ * {@link TemplateNumberFormatFactory} implementation. Not {@code null}, often an empty string.
+ * @param locale
+ * The locale to format for. Not {@code null}. The resulting format must be bound to this locale
+ * forever (i.e. locale changes in the {@link Environment} must not be followed).
+ * @param env
+ * The runtime environment from which the formatting was called. This is mostly meant to be used for
+ * {@link Environment#getCustomState(CustomStateKey)}.
+ *
+ * @throws TemplateValueFormatException
+ * if any problem occurs while parsing/getting the format. Notable subclasses:
+ * {@link InvalidFormatParametersException} if the {@code params} is malformed.
+ */
+ public abstract TemplateNumberFormat get(String params, Locale locale, Environment env)
+ throws TemplateValueFormatException;
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java
new file mode 100644
index 0000000..9203e5a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormat.java
@@ -0,0 +1,42 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat;
+
+/**
+ * Superclass of all value format objects; objects that convert values to strings, or parse strings.
+ *
+ * @since 2.3.24
+ */
+public abstract class TemplateValueFormat {
+
+ /**
+ * Meant to be used in error messages to tell what format the parsed string didn't fit.
+ */
+ public abstract String getDescription();
+
+ /**
+ * The implementation in {@link TemplateValueFormat} returns {@code package.className(description)}, where
+ * description comes from {@link #getDescription()}.
+ */
+ @Override
+ public String toString() {
+ return getClass().getName() + "(" + getDescription() + ")";
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatException.java
new file mode 100644
index 0000000..dd538e6
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatException.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat;
+
+/**
+ * Error while getting, creating or applying {@link TemplateValueFormat}-s (including its subclasses, like
+ * {@link TemplateNumberFormat}).
+ *
+ * @since 2.3.24
+ */
+public abstract class TemplateValueFormatException extends Exception {
+
+ public TemplateValueFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public TemplateValueFormatException(String message) {
+ super(message);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatFactory.java
new file mode 100644
index 0000000..04f706e
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/TemplateValueFormatFactory.java
@@ -0,0 +1,28 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat;
+
+/**
+ * Superclass of all format factories.
+ *
+ * @since 2.3.24
+ */
+public abstract class TemplateValueFormatFactory {
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UndefinedCustomFormatException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UndefinedCustomFormatException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UndefinedCustomFormatException.java
new file mode 100644
index 0000000..fa4ed3d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UndefinedCustomFormatException.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat;
+
+/**
+ * @since 2.3.24
+ */
+public class UndefinedCustomFormatException extends InvalidFormatStringException {
+
+ public UndefinedCustomFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UndefinedCustomFormatException(String message) {
+ super(message);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnformattableValueException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnformattableValueException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnformattableValueException.java
new file mode 100644
index 0000000..6ef3b10
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/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 org.apache.freemarker.core.valueformat;
+
+import org.apache.freemarker.core.model.TemplateModel;
+
+/**
+ * Thrown when a {@link TemplateModel} can't be formatted because of the value/properties of it are outside of that the
+ * {@link TemplateValueFormat} supports. For example, a formatter may not support dates before year 1, or can't format
+ * NaN. The most often used subclass is {@link UnknownDateTypeFormattingUnsupportedException}.
+ *
+ * @since 2.3.24
+ */
+public class UnformattableValueException extends TemplateValueFormatException {
+
+ public UnformattableValueException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public UnformattableValueException(String message) {
+ super(message);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeFormattingUnsupportedException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeFormattingUnsupportedException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeFormattingUnsupportedException.java
new file mode 100644
index 0000000..90ae4be
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeFormattingUnsupportedException.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 org.apache.freemarker.core.valueformat;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+
+/**
+ * Thrown when a {@link TemplateDateModel} can't be formatted because its type is {@link TemplateDateModel#UNKNOWN}.
+ *
+ * @since 2.3.24
+ */
+public final class UnknownDateTypeFormattingUnsupportedException extends UnformattableValueException {
+
+ public UnknownDateTypeFormattingUnsupportedException() {
+ super("Can't format the 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.");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeParsingUnsupportedException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeParsingUnsupportedException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeParsingUnsupportedException.java
new file mode 100644
index 0000000..ef6cca2
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnknownDateTypeParsingUnsupportedException.java
@@ -0,0 +1,37 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.valueformat;
+
+import org.apache.freemarker.core.model.TemplateDateModel;
+
+/**
+ * Thrown when a string can't be parsed to {@link TemplateDateModel}, because the provided target type is
+ * {@link TemplateDateModel#UNKNOWN}.
+ *
+ * @since 2.3.24
+ */
+public final class UnknownDateTypeParsingUnsupportedException extends UnformattableValueException {
+
+ public UnknownDateTypeParsingUnsupportedException() {
+ super("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.");
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnparsableValueException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnparsableValueException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/UnparsableValueException.java
new file mode 100644
index 0000000..78af935
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/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 org.apache.freemarker.core.valueformat;
+
+/**
+ * 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/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.java
new file mode 100644
index 0000000..b4625a4
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTargetTemplateValueFormatException.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 org.apache.freemarker.core.valueformat.impl;
+
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+
+/**
+ * Can't invoke a template format that the template format refers to (typically thrown by alias template formats).
+ *
+ * @since 2.3.24
+ */
+class AliasTargetTemplateValueFormatException extends TemplateValueFormatException {
+
+ public AliasTargetTemplateValueFormatException(String message, Throwable cause) {
+ super(message, cause);
+ }
+
+ public AliasTargetTemplateValueFormatException(String message) {
+ super(message);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java
new file mode 100644
index 0000000..a964bc2
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateDateFormatFactory.java
@@ -0,0 +1,97 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat.impl;
+
+import java.util.Locale;
+import java.util.Map;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.util._LocaleUtil;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateFormatUtil;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+
+/**
+ * Creates an alias to another format, so that the format can be referred to with a simple name in the template, rather
+ * than as a concrete pattern or other kind of format string.
+ *
+ * @since 2.3.24
+ */
+public final class AliasTemplateDateFormatFactory extends TemplateDateFormatFactory {
+
+ private final String defaultTargetFormatString;
+ private final Map<Locale, String> localizedTargetFormatStrings;
+
+ /**
+ * @param targetFormatString
+ * The format string this format will be an alias to.
+ */
+ public AliasTemplateDateFormatFactory(String targetFormatString) {
+ defaultTargetFormatString = targetFormatString;
+ localizedTargetFormatStrings = null;
+ }
+
+ /**
+ * @param defaultTargetFormatString
+ * The format string this format will be an alias to if there's no locale-specific format string for the
+ * requested locale in {@code localizedTargetFormatStrings}
+ * @param localizedTargetFormatStrings
+ * Maps {@link Locale}-s to format strings. If the desired locale doesn't occur in the map, a less
+ * specific locale is tried, repeatedly until only the language part remains. For example, if locale is
+ * {@code new Locale("en", "US", "Linux")}, then these keys will be attempted untol a match is found, in
+ * this order: {@code new Locale("en", "US", "Linux")}, {@code new Locale("en", "US")},
+ * {@code new Locale("en")}. If there's still no matching key, the value of the
+ * {@code targetFormatString} will be used.
+ */
+ public AliasTemplateDateFormatFactory(
+ String defaultTargetFormatString, Map<Locale, String> localizedTargetFormatStrings) {
+ this.defaultTargetFormatString = defaultTargetFormatString;
+ this.localizedTargetFormatStrings = localizedTargetFormatStrings;
+ }
+
+ @Override
+ public TemplateDateFormat get(String params, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput,
+ Environment env) throws TemplateValueFormatException {
+ TemplateFormatUtil.checkHasNoParameters(params);
+ try {
+ String targetFormatString;
+ if (localizedTargetFormatStrings != null) {
+ Locale lookupLocale = locale;
+ targetFormatString = localizedTargetFormatStrings.get(lookupLocale);
+ while (targetFormatString == null
+ && (lookupLocale = _LocaleUtil.getLessSpecificLocale(lookupLocale)) != null) {
+ targetFormatString = localizedTargetFormatStrings.get(lookupLocale);
+ }
+ } else {
+ targetFormatString = null;
+ }
+ if (targetFormatString == null) {
+ targetFormatString = defaultTargetFormatString;
+ }
+ return env.getTemplateDateFormat(targetFormatString, dateType, locale, timeZone, zonelessInput);
+ } catch (TemplateValueFormatException e) {
+ throw new AliasTargetTemplateValueFormatException("Failed to invoke format based on target format string, "
+ + _StringUtil.jQuote(params) + ". Reason given: " + e.getMessage(), e);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java
new file mode 100644
index 0000000..72e8abd
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/AliasTemplateNumberFormatFactory.java
@@ -0,0 +1,96 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat.impl;
+
+import java.util.Locale;
+import java.util.Map;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.util._LocaleUtil;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.TemplateFormatUtil;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormat;
+import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
+import org.apache.freemarker.core.valueformat.TemplateValueFormatException;
+
+/**
+ * Creates an alias to another format, so that the format can be referred to with a simple name in the template, rather
+ * than as a concrete pattern or other kind of format string.
+ *
+ * @since 2.3.24
+ */
+public final class AliasTemplateNumberFormatFactory extends TemplateNumberFormatFactory {
+
+ private final String defaultTargetFormatString;
+ private final Map<Locale, String> localizedTargetFormatStrings;
+
+ /**
+ * @param targetFormatString
+ * The format string this format will be an alias to
+ */
+ public AliasTemplateNumberFormatFactory(String targetFormatString) {
+ defaultTargetFormatString = targetFormatString;
+ localizedTargetFormatStrings = null;
+ }
+
+ /**
+ * @param defaultTargetFormatString
+ * The format string this format will be an alias to if there's no locale-specific format string for the
+ * requested locale in {@code localizedTargetFormatStrings}
+ * @param localizedTargetFormatStrings
+ * Maps {@link Locale}-s to format strings. If the desired locale doesn't occur in the map, a less
+ * specific locale is tried, repeatedly until only the language part remains. For example, if locale is
+ * {@code new Locale("en", "US", "Linux")}, then these keys will be attempted untol a match is found, in
+ * this order: {@code new Locale("en", "US", "Linux")}, {@code new Locale("en", "US")},
+ * {@code new Locale("en")}. If there's still no matching key, the value of the
+ * {@code targetFormatString} will be used.
+ */
+ public AliasTemplateNumberFormatFactory(
+ String defaultTargetFormatString, Map<Locale, String> localizedTargetFormatStrings) {
+ this.defaultTargetFormatString = defaultTargetFormatString;
+ this.localizedTargetFormatStrings = localizedTargetFormatStrings;
+ }
+
+ @Override
+ public TemplateNumberFormat get(String params, Locale locale, Environment env)
+ throws TemplateValueFormatException {
+ TemplateFormatUtil.checkHasNoParameters(params);
+ try {
+ String targetFormatString;
+ if (localizedTargetFormatStrings != null) {
+ Locale lookupLocale = locale;
+ targetFormatString = localizedTargetFormatStrings.get(lookupLocale);
+ while (targetFormatString == null
+ && (lookupLocale = _LocaleUtil.getLessSpecificLocale(lookupLocale)) != null) {
+ targetFormatString = localizedTargetFormatStrings.get(lookupLocale);
+ }
+ } else {
+ targetFormatString = null;
+ }
+ if (targetFormatString == null) {
+ targetFormatString = defaultTargetFormatString;
+ }
+ return env.getTemplateNumberFormat(targetFormatString, locale);
+ } catch (TemplateValueFormatException e) {
+ throw new AliasTargetTemplateValueFormatException("Failed to invoke format based on target format string, "
+ + _StringUtil.jQuote(params) + ". Reason given: " + e.getMessage(), e);
+ }
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatParser.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatParser.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatParser.java
new file mode 100644
index 0000000..dc1709c
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ExtendedDecimalFormatParser.java
@@ -0,0 +1,530 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.valueformat.impl;
+
+import java.math.RoundingMode;
+import java.text.DecimalFormat;
+import java.text.DecimalFormatSymbols;
+import java.text.ParseException;
+import java.util.Arrays;
+import java.util.Currency;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Set;
+
+import org.apache.freemarker.core.util._StringUtil;
+
+/**
+ * Parses a {@link DecimalFormat} pattern string to a {@link DecimalFormat} instance, with the pattern string extensions
+ * described in the Manual (see "Extended Java decimal format"). The result is a standard {@link DecimalFormat} object,
+ * but further configured according the extension part.
+ */
+class ExtendedDecimalFormatParser {
+
+ private static final String PARAM_ROUNDING_MODE = "roundingMode";
+ private static final String PARAM_MULTIPIER = "multipier";
+ private static final String PARAM_DECIMAL_SEPARATOR = "decimalSeparator";
+ private static final String PARAM_MONETARY_DECIMAL_SEPARATOR = "monetaryDecimalSeparator";
+ private static final String PARAM_GROUP_SEPARATOR = "groupingSeparator";
+ private static final String PARAM_EXPONENT_SEPARATOR = "exponentSeparator";
+ private static final String PARAM_MINUS_SIGN = "minusSign";
+ private static final String PARAM_INFINITY = "infinity";
+ private static final String PARAM_NAN = "nan";
+ private static final String PARAM_PERCENT = "percent";
+ private static final String PARAM_PER_MILL = "perMill";
+ private static final String PARAM_ZERO_DIGIT = "zeroDigit";
+ private static final String PARAM_CURRENCY_CODE = "currencyCode";
+ private static final String PARAM_CURRENCY_SYMBOL = "currencySymbol";
+
+ private static final String PARAM_VALUE_RND_UP = "up";
+ private static final String PARAM_VALUE_RND_DOWN = "down";
+ private static final String PARAM_VALUE_RND_CEILING = "ceiling";
+ private static final String PARAM_VALUE_RND_FLOOR = "floor";
+ private static final String PARAM_VALUE_RND_HALF_DOWN = "halfDown";
+ private static final String PARAM_VALUE_RND_HALF_EVEN = "halfEven";
+ private static final String PARAM_VALUE_RND_HALF_UP = "halfUp";
+ private static final String PARAM_VALUE_RND_UNNECESSARY = "unnecessary";
+
+ private static final HashMap<String, ? extends ParameterHandler> PARAM_HANDLERS;
+ static {
+ HashMap<String, ParameterHandler> m = new HashMap<>();
+ m.put(PARAM_ROUNDING_MODE, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ RoundingMode parsedValue;
+ if (value.equals(PARAM_VALUE_RND_UP)) {
+ parsedValue = RoundingMode.UP;
+ } else if (value.equals(PARAM_VALUE_RND_DOWN)) {
+ parsedValue = RoundingMode.DOWN;
+ } else if (value.equals(PARAM_VALUE_RND_CEILING)) {
+ parsedValue = RoundingMode.CEILING;
+ } else if (value.equals(PARAM_VALUE_RND_FLOOR)) {
+ parsedValue = RoundingMode.FLOOR;
+ } else if (value.equals(PARAM_VALUE_RND_HALF_DOWN)) {
+ parsedValue = RoundingMode.HALF_DOWN;
+ } else if (value.equals(PARAM_VALUE_RND_HALF_EVEN)) {
+ parsedValue = RoundingMode.HALF_EVEN;
+ } else if (value.equals(PARAM_VALUE_RND_HALF_UP)) {
+ parsedValue = RoundingMode.HALF_UP;
+ } else if (value.equals(PARAM_VALUE_RND_UNNECESSARY)) {
+ parsedValue = RoundingMode.UNNECESSARY;
+ } else {
+ throw new InvalidParameterValueException("Should be one of: u, d, c, f, hd, he, hu, un");
+ }
+
+ parser.roundingMode = parsedValue;
+ }
+ });
+ m.put(PARAM_MULTIPIER, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ try {
+ parser.multipier = Integer.valueOf(value);
+ } catch (NumberFormatException e) {
+ throw new InvalidParameterValueException("Malformed integer.");
+ }
+ }
+ });
+ m.put(PARAM_DECIMAL_SEPARATOR, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ if (value.length() != 1) {
+ throw new InvalidParameterValueException("Must contain exactly 1 character.");
+ }
+ parser.symbols.setDecimalSeparator(value.charAt(0));
+ }
+ });
+ m.put(PARAM_MONETARY_DECIMAL_SEPARATOR, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ if (value.length() != 1) {
+ throw new InvalidParameterValueException("Must contain exactly 1 character.");
+ }
+ parser.symbols.setMonetaryDecimalSeparator(value.charAt(0));
+ }
+ });
+ m.put(PARAM_GROUP_SEPARATOR, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ if (value.length() != 1) {
+ throw new InvalidParameterValueException("Must contain exactly 1 character.");
+ }
+ parser.symbols.setGroupingSeparator(value.charAt(0));
+ }
+ });
+ m.put(PARAM_EXPONENT_SEPARATOR, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ parser.symbols.setExponentSeparator(value);
+ }
+ });
+ m.put(PARAM_MINUS_SIGN, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ if (value.length() != 1) {
+ throw new InvalidParameterValueException("Must contain exactly 1 character.");
+ }
+ parser.symbols.setMinusSign(value.charAt(0));
+ }
+ });
+ m.put(PARAM_INFINITY, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ parser.symbols.setInfinity(value);
+ }
+ });
+ m.put(PARAM_NAN, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ parser.symbols.setNaN(value);
+ }
+ });
+ m.put(PARAM_PERCENT, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ if (value.length() != 1) {
+ throw new InvalidParameterValueException("Must contain exactly 1 character.");
+ }
+ parser.symbols.setPercent(value.charAt(0));
+ }
+ });
+ m.put(PARAM_PER_MILL, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ if (value.length() != 1) {
+ throw new InvalidParameterValueException("Must contain exactly 1 character.");
+ }
+ parser.symbols.setPerMill(value.charAt(0));
+ }
+ });
+ m.put(PARAM_ZERO_DIGIT, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ if (value.length() != 1) {
+ throw new InvalidParameterValueException("Must contain exactly 1 character.");
+ }
+ parser.symbols.setZeroDigit(value.charAt(0));
+ }
+ });
+ m.put(PARAM_CURRENCY_CODE, new ParameterHandler() {
+ @Override
+ public void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException {
+ Currency currency;
+ try {
+ currency = Currency.getInstance(value);
+ } catch (IllegalArgumentException e) {
+ throw new InvalidParameterValueException("Not a known ISO 4217 code.");
+ }
+ parser.symbols.setCurrency(currency);
+ }
+ });
+ PARAM_HANDLERS = m;
+ }
+
+ private static final String SNIP_MARK = "[...]";
+ private static final int MAX_QUOTATION_LENGTH = 10; // Must be more than SNIP_MARK.length!
+
+ private final String src;
+ private int pos = 0;
+
+ private final DecimalFormatSymbols symbols;
+ private RoundingMode roundingMode;
+ private Integer multipier;
+
+ static DecimalFormat parse(String formatString, Locale locale) throws ParseException {
+ return new ExtendedDecimalFormatParser(formatString, locale).parse();
+ }
+
+ private DecimalFormat parse() throws ParseException {
+ String stdPattern = fetchStandardPattern();
+ skipWS();
+ parseFormatStringExtension();
+
+ DecimalFormat decimalFormat;
+ try {
+ decimalFormat = new DecimalFormat(stdPattern, symbols);
+ } catch (IllegalArgumentException e) {
+ ParseException pe = new ParseException(e.getMessage(), 0);
+ if (e.getCause() != null) {
+ try {
+ e.initCause(e.getCause());
+ } catch (Exception e2) {
+ // Supress
+ }
+ }
+ throw pe;
+ }
+
+ if (roundingMode != null) {
+ decimalFormat.setRoundingMode(roundingMode);
+ }
+
+ if (multipier != null) {
+ decimalFormat.setMultiplier(multipier.intValue());
+ }
+
+ return decimalFormat;
+ }
+
+ private void parseFormatStringExtension() throws ParseException {
+ int ln = src.length();
+
+ if (pos == ln) {
+ return;
+ }
+
+ String currencySymbol = null; // Exceptional, as must be applied after "currency code"
+ fetchParamters: do {
+ int namePos = pos;
+ String name = fetchName();
+ if (name == null) {
+ throw newExpectedSgParseException("name");
+ }
+
+ skipWS();
+
+ if (!fetchChar('=')) {
+ throw newExpectedSgParseException("\"=\"");
+ }
+
+ skipWS();
+
+ int valuePos = pos;
+ String value = fetchValue();
+ if (value == null) {
+ throw newExpectedSgParseException("value");
+ }
+ int paramEndPos = pos;
+
+ ParameterHandler handler = PARAM_HANDLERS.get(name);
+ if (handler == null) {
+ if (name.equals(PARAM_CURRENCY_SYMBOL)) {
+ currencySymbol = value;
+ } else {
+ throw newUnknownParameterException(name, namePos);
+ }
+ } else {
+ try {
+ handler.handle(this, value);
+ } catch (InvalidParameterValueException e) {
+ throw newInvalidParameterValueException(name, value, valuePos, e);
+ }
+ }
+
+ skipWS();
+
+ // Optional comma
+ if (fetchChar(',')) {
+ skipWS();
+ } else {
+ if (pos == ln) {
+ break fetchParamters;
+ }
+ if (pos == paramEndPos) {
+ throw newExpectedSgParseException("parameter separator whitespace or comma");
+ }
+ }
+ } while (true);
+
+ // This is brought out to here to ensure that it's applied after "currency code":
+ if (currencySymbol != null) {
+ symbols.setCurrencySymbol(currencySymbol);
+ }
+ }
+
+ private ParseException newInvalidParameterValueException(String name, String value, int valuePos,
+ InvalidParameterValueException e) {
+ return new java.text.ParseException(
+ _StringUtil.jQuote(value) + " is an invalid value for the \"" + name + "\" parameter: "
+ + e.message,
+ valuePos);
+ }
+
+ private ParseException newUnknownParameterException(String name, int namePos) throws ParseException {
+ StringBuilder sb = new StringBuilder(128);
+ sb.append("Unsupported parameter name, ").append(_StringUtil.jQuote(name));
+ sb.append(". The supported names are: ");
+ Set<String> legalNames = PARAM_HANDLERS.keySet();
+ String[] legalNameArr = legalNames.toArray(new String[legalNames.size()]);
+ Arrays.sort(legalNameArr);
+ for (int i = 0; i < legalNameArr.length; i++) {
+ if (i != 0) {
+ sb.append(", ");
+ }
+ sb.append(legalNameArr[i]);
+ }
+ return new java.text.ParseException(sb.toString(), namePos);
+ }
+
+ private void skipWS() {
+ int ln = src.length();
+ while (pos < ln && isWS(src.charAt(pos))) {
+ pos++;
+ }
+ }
+
+ private boolean fetchChar(char fetchedChar) {
+ if (pos < src.length() && src.charAt(pos) == fetchedChar) {
+ pos++;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ private boolean isWS(char c) {
+ return c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '\u00A0';
+ }
+
+ private String fetchName() throws ParseException {
+ int ln = src.length();
+ int startPos = pos;
+ boolean firstChar = true;
+ scanUntilEnd: while (pos < ln) {
+ char c = src.charAt(pos);
+ if (firstChar) {
+ if (!Character.isJavaIdentifierStart(c)) {
+ break scanUntilEnd;
+ }
+ firstChar = false;
+ } else if (!Character.isJavaIdentifierPart(c)) {
+ break scanUntilEnd;
+ }
+ pos++;
+ }
+ return !firstChar ? src.substring(startPos, pos) : null;
+ }
+
+ private String fetchValue() throws ParseException {
+ int ln = src.length();
+ int startPos = pos;
+ char openedQuot = 0;
+ boolean needsUnescaping = false;
+ scanUntilEnd: while (pos < ln) {
+ char c = src.charAt(pos);
+ if (c == '\'' || c == '"') {
+ if (openedQuot == 0) {
+ if (startPos != pos) {
+ throw new java.text.ParseException(
+ "The " + c + " character can only be used for quoting values, "
+ + "but it was in the middle of an non-quoted value.",
+ pos);
+ }
+ openedQuot = c;
+ } else if (c == openedQuot) {
+ if (pos + 1 < ln && src.charAt(pos + 1) == openedQuot) {
+ pos++; // skip doubled quote (escaping)
+ needsUnescaping = true;
+ } else {
+ String str = src.substring(startPos + 1, pos);
+ pos++;
+ return needsUnescaping ? unescape(str, openedQuot) : str;
+ }
+ }
+ } else {
+ if (openedQuot == 0 && !Character.isJavaIdentifierPart(c)) {
+ break scanUntilEnd;
+ }
+ }
+ pos++;
+ } // while
+ if (openedQuot != 0) {
+ throw new java.text.ParseException(
+ "The " + openedQuot
+ + " quotation wasn't closed when the end of the source was reached.",
+ pos);
+ }
+ return startPos == pos ? null : src.substring(startPos, pos);
+ }
+
+ private String unescape(String s, char openedQuot) {
+ return openedQuot == '\'' ? _StringUtil.replace(s, "\'\'", "\'") : _StringUtil.replace(s, "\"\"", "\"");
+ }
+
+ private String fetchStandardPattern() {
+ int pos = this.pos;
+ int ln = src.length();
+ int semicolonCnt = 0;
+ boolean quotedMode = false;
+ findStdPartEnd: while (pos < ln) {
+ char c = src.charAt(pos);
+ if (c == ';' && !quotedMode) {
+ semicolonCnt++;
+ if (semicolonCnt == 2) {
+ break findStdPartEnd;
+ }
+ } else if (c == '\'') {
+ if (quotedMode) {
+ if (pos + 1 < ln && src.charAt(pos + 1) == '\'') {
+ // Skips "''" used for escaping "'"
+ pos++;
+ } else {
+ quotedMode = false;
+ }
+ } else {
+ quotedMode = true;
+ }
+ }
+ pos++;
+ }
+
+ String stdFormatStr;
+ if (semicolonCnt < 2) { // We have a standard DecimalFormat string
+ // Note that "0.0;" and "0.0" gives the same result with DecimalFormat, so we leave a ';' there
+ stdFormatStr = src;
+ } else { // `pos` points to the 2nd ';'
+ int stdEndPos = pos;
+ if (src.charAt(pos - 1) == ';') { // we have a ";;"
+ // Note that ";;" is illegal in DecimalFormat, so this is backward compatible.
+ stdEndPos--;
+ }
+ stdFormatStr = src.substring(0, stdEndPos);
+ }
+
+ if (pos < ln) {
+ pos++; // Skips closing ';'
+ }
+ this.pos = pos;
+
+ return stdFormatStr;
+ }
+
+ private ExtendedDecimalFormatParser(String formatString, Locale locale) {
+ src = formatString;
+ symbols = new DecimalFormatSymbols(locale);
+ }
+
+ private ParseException newExpectedSgParseException(String expectedThing) {
+ String quotation;
+
+ // Ignore trailing WS when calculating the length:
+ int i = src.length() - 1;
+ while (i >= 0 && Character.isWhitespace(src.charAt(i))) {
+ i--;
+ }
+ int ln = i + 1;
+
+ if (pos < ln) {
+ int qEndPos = pos + MAX_QUOTATION_LENGTH;
+ if (qEndPos >= ln) {
+ quotation = src.substring(pos, ln);
+ } else {
+ quotation = src.substring(pos, qEndPos - SNIP_MARK.length()) + SNIP_MARK;
+ }
+ } else {
+ quotation = null;
+ }
+
+ return new ParseException(
+ "Expected a(n) " + expectedThing + " at position " + pos + " (0-based), but "
+ + (quotation == null ? "reached the end of the input." : "found: " + quotation),
+ pos);
+ }
+
+ private interface ParameterHandler {
+
+ void handle(ExtendedDecimalFormatParser parser, String value)
+ throws InvalidParameterValueException;
+
+ }
+
+ private static class InvalidParameterValueException extends Exception {
+
+ private final String message;
+
+ public InvalidParameterValueException(String message) {
+ this.message = message;
+ }
+
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormat.java
new file mode 100644
index 0000000..8790d00
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormat.java
@@ -0,0 +1,270 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.valueformat.impl;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateModelException;
+import org.apache.freemarker.core.util.BugException;
+import org.apache.freemarker.core.util._DateUtil;
+import org.apache.freemarker.core.util._DateUtil.CalendarFieldsToDateConverter;
+import org.apache.freemarker.core.util._DateUtil.DateParseException;
+import org.apache.freemarker.core.util._DateUtil.DateToISO8601CalendarFactory;
+import org.apache.freemarker.core.util._StringUtil;
+import org.apache.freemarker.core.valueformat.InvalidFormatParametersException;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.TemplateFormatUtil;
+import org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException;
+import org.apache.freemarker.core.valueformat.UnparsableValueException;
+
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
+
+abstract class ISOLikeTemplateDateFormat extends TemplateDateFormat {
+
+ private static final String XS_LESS_THAN_SECONDS_ACCURACY_ERROR_MESSAGE
+ = "Less than seconds accuracy isn't allowed by the XML Schema format";
+ private final ISOLikeTemplateDateFormatFactory factory;
+ private final Environment env;
+ protected final int dateType;
+ protected final boolean zonelessInput;
+ protected final TimeZone timeZone;
+ protected final Boolean forceUTC;
+ protected final Boolean showZoneOffset;
+ protected final int accuracy;
+
+ /**
+ * @param formatString The value of the ..._format setting, like "iso nz".
+ * @param parsingStart The index of the char in the {@code settingValue} that directly after the prefix that has
+ * indicated the exact formatter class (like "iso" or "xs")
+ */
+ public ISOLikeTemplateDateFormat(
+ final String formatString, int parsingStart,
+ int dateType, boolean zonelessInput,
+ TimeZone timeZone,
+ ISOLikeTemplateDateFormatFactory factory, Environment env)
+ throws InvalidFormatParametersException, UnknownDateTypeFormattingUnsupportedException {
+ this.factory = factory;
+ this.env = env;
+ if (dateType == TemplateDateModel.UNKNOWN) {
+ throw new UnknownDateTypeFormattingUnsupportedException();
+ }
+
+ this.dateType = dateType;
+ this.zonelessInput = zonelessInput;
+
+ final int ln = formatString.length();
+ boolean afterSeparator = false;
+ int i = parsingStart;
+ int accuracy = _DateUtil.ACCURACY_MILLISECONDS;
+ Boolean showZoneOffset = null;
+ Boolean forceUTC = Boolean.FALSE;
+ while (i < ln) {
+ final char c = formatString.charAt(i++);
+ if (c == '_' || c == ' ') {
+ afterSeparator = true;
+ } else {
+ if (!afterSeparator) {
+ throw new InvalidFormatParametersException(
+ "Missing space or \"_\" before \"" + c + "\" (at char pos. " + i + ").");
+ }
+
+ switch (c) {
+ case 'h':
+ case 'm':
+ case 's':
+ if (accuracy != _DateUtil.ACCURACY_MILLISECONDS) {
+ throw new InvalidFormatParametersException(
+ "Character \"" + c + "\" is unexpected as accuracy was already specified earlier "
+ + "(at char pos. " + i + ").");
+ }
+ switch (c) {
+ case 'h':
+ if (isXSMode()) {
+ throw new InvalidFormatParametersException(
+ XS_LESS_THAN_SECONDS_ACCURACY_ERROR_MESSAGE);
+ }
+ accuracy = _DateUtil.ACCURACY_HOURS;
+ break;
+ case 'm':
+ if (i < ln && formatString.charAt(i) == 's') {
+ i++;
+ accuracy = _DateUtil.ACCURACY_MILLISECONDS_FORCED;
+ } else {
+ if (isXSMode()) {
+ throw new InvalidFormatParametersException(
+ XS_LESS_THAN_SECONDS_ACCURACY_ERROR_MESSAGE);
+ }
+ accuracy = _DateUtil.ACCURACY_MINUTES;
+ }
+ break;
+ case 's':
+ accuracy = _DateUtil.ACCURACY_SECONDS;
+ break;
+ }
+ break;
+ case 'f':
+ if (i < ln && formatString.charAt(i) == 'u') {
+ checkForceUTCNotSet(forceUTC);
+ i++;
+ forceUTC = Boolean.TRUE;
+ break;
+ }
+ // Falls through
+ case 'n':
+ if (showZoneOffset != null) {
+ throw new InvalidFormatParametersException(
+ "Character \"" + c + "\" is unexpected as zone offset visibility was already "
+ + "specified earlier. (at char pos. " + i + ").");
+ }
+ switch (c) {
+ case 'n':
+ if (i < ln && formatString.charAt(i) == 'z') {
+ i++;
+ showZoneOffset = Boolean.FALSE;
+ } else {
+ throw new InvalidFormatParametersException(
+ "\"n\" must be followed by \"z\" (at char pos. " + i + ").");
+ }
+ break;
+ case 'f':
+ if (i < ln && formatString.charAt(i) == 'z') {
+ i++;
+ showZoneOffset = Boolean.TRUE;
+ } else {
+ throw new InvalidFormatParametersException(
+ "\"f\" must be followed by \"z\" (at char pos. " + i + ").");
+ }
+ break;
+ }
+ break;
+ case 'u':
+ checkForceUTCNotSet(forceUTC);
+ forceUTC = null; // means UTC will be used except for zonelessInput
+ break;
+ default:
+ throw new InvalidFormatParametersException(
+ "Unexpected character, " + _StringUtil.jQuote(String.valueOf(c))
+ + ". Expected the beginning of one of: h, m, s, ms, nz, fz, u"
+ + " (at char pos. " + i + ").");
+ } // switch
+ afterSeparator = false;
+ } // else
+ } // while
+
+ this.accuracy = accuracy;
+ this.showZoneOffset = showZoneOffset;
+ this.forceUTC = forceUTC;
+ this.timeZone = timeZone;
+ }
+
+ private void checkForceUTCNotSet(Boolean fourceUTC) throws InvalidFormatParametersException {
+ if (fourceUTC != Boolean.FALSE) {
+ throw new InvalidFormatParametersException(
+ "The UTC usage option was already set earlier.");
+ }
+ }
+
+ @Override
+ public final String formatToPlainText(TemplateDateModel dateModel) throws TemplateModelException {
+ final Date date = TemplateFormatUtil.getNonNullDate(dateModel);
+ return format(
+ date,
+ dateType != TemplateDateModel.TIME,
+ dateType != TemplateDateModel.DATE,
+ showZoneOffset == null
+ ? !zonelessInput
+ : showZoneOffset.booleanValue(),
+ accuracy,
+ (forceUTC == null ? !zonelessInput : forceUTC.booleanValue()) ? _DateUtil.UTC : timeZone,
+ factory.getISOBuiltInCalendar(env));
+ }
+
+ protected abstract String format(Date date,
+ boolean datePart, boolean timePart, boolean offsetPart,
+ int accuracy,
+ TimeZone timeZone,
+ DateToISO8601CalendarFactory calendarFactory);
+
+ @Override
+ @SuppressFBWarnings(value = "RC_REF_COMPARISON_BAD_PRACTICE_BOOLEAN",
+ justification = "Known to use the singleton Boolean-s only")
+ public final Date parse(String s, int dateType) throws UnparsableValueException {
+ CalendarFieldsToDateConverter calToDateConverter = factory.getCalendarFieldsToDateCalculator(env);
+ TimeZone tz = forceUTC != Boolean.FALSE ? _DateUtil.UTC : timeZone;
+ 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);
+ }
+ }
+
+ protected abstract Date parseDate(
+ String s, TimeZone tz,
+ CalendarFieldsToDateConverter calToDateConverter)
+ throws DateParseException;
+
+ protected abstract Date parseTime(
+ String s, TimeZone tz,
+ CalendarFieldsToDateConverter calToDateConverter)
+ throws DateParseException;
+
+ protected abstract Date parseDateTime(
+ String s, TimeZone tz,
+ CalendarFieldsToDateConverter calToDateConverter)
+ throws DateParseException;
+
+ @Override
+ public final String getDescription() {
+ switch (dateType) {
+ case TemplateDateModel.DATE: return getDateDescription();
+ case TemplateDateModel.TIME: return getTimeDescription();
+ case TemplateDateModel.DATETIME: return getDateTimeDescription();
+ default: return "<error: wrong format dateType>";
+ }
+ }
+
+ protected abstract String getDateDescription();
+ protected abstract String getTimeDescription();
+ protected abstract String getDateTimeDescription();
+
+ @Override
+ public final boolean isLocaleBound() {
+ return false;
+ }
+
+ @Override
+ public boolean isTimeZoneBound() {
+ return true;
+ }
+
+ protected abstract boolean isXSMode();
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java
new file mode 100644
index 0000000..5db8f46
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOLikeTemplateDateFormatFactory.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.valueformat.impl;
+
+import org.apache.freemarker.core.CustomStateKey;
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.util._DateUtil.CalendarFieldsToDateConverter;
+import org.apache.freemarker.core.util._DateUtil.DateToISO8601CalendarFactory;
+import org.apache.freemarker.core.util._DateUtil.TrivialCalendarFieldsToDateConverter;
+import org.apache.freemarker.core.util._DateUtil.TrivialDateToISO8601CalendarFactory;
+import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
+
+abstract class ISOLikeTemplateDateFormatFactory extends TemplateDateFormatFactory {
+
+ private static final CustomStateKey<TrivialDateToISO8601CalendarFactory> DATE_TO_CAL_CONVERTER_KEY
+ = new CustomStateKey<TrivialDateToISO8601CalendarFactory>() {
+ @Override
+ protected TrivialDateToISO8601CalendarFactory create() {
+ return new TrivialDateToISO8601CalendarFactory();
+ }
+ };
+ private static final CustomStateKey<TrivialCalendarFieldsToDateConverter> CAL_TO_DATE_CONVERTER_KEY
+ = new CustomStateKey<TrivialCalendarFieldsToDateConverter>() {
+ @Override
+ protected TrivialCalendarFieldsToDateConverter create() {
+ return new TrivialCalendarFieldsToDateConverter();
+ }
+ };
+
+ protected ISOLikeTemplateDateFormatFactory() { }
+
+ public DateToISO8601CalendarFactory getISOBuiltInCalendar(Environment env) {
+ return (DateToISO8601CalendarFactory) env.getCustomState(DATE_TO_CAL_CONVERTER_KEY);
+ }
+
+ public CalendarFieldsToDateConverter getCalendarFieldsToDateCalculator(Environment env) {
+ return (CalendarFieldsToDateConverter) env.getCustomState(CAL_TO_DATE_CONVERTER_KEY);
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormat.java
new file mode 100644
index 0000000..4856ee0
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormat.java
@@ -0,0 +1,90 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.valueformat.impl;
+
+import java.util.Date;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.util._DateUtil;
+import org.apache.freemarker.core.util._DateUtil.CalendarFieldsToDateConverter;
+import org.apache.freemarker.core.util._DateUtil.DateParseException;
+import org.apache.freemarker.core.util._DateUtil.DateToISO8601CalendarFactory;
+import org.apache.freemarker.core.valueformat.InvalidFormatParametersException;
+import org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException;
+
+class ISOTemplateDateFormat extends ISOLikeTemplateDateFormat {
+
+ ISOTemplateDateFormat(
+ String settingValue, int parsingStart,
+ int dateType, boolean zonelessInput,
+ TimeZone timeZone,
+ ISOLikeTemplateDateFormatFactory factory,
+ Environment env)
+ throws InvalidFormatParametersException, UnknownDateTypeFormattingUnsupportedException {
+ super(settingValue, parsingStart, dateType, zonelessInput, timeZone, factory, env);
+ }
+
+ @Override
+ protected String format(Date date, boolean datePart, boolean timePart, boolean offsetPart, int accuracy,
+ TimeZone timeZone, DateToISO8601CalendarFactory calendarFactory) {
+ return _DateUtil.dateToISO8601String(
+ date, datePart, timePart, timePart && offsetPart, accuracy, timeZone, calendarFactory);
+ }
+
+ @Override
+ protected Date parseDate(String s, TimeZone tz, CalendarFieldsToDateConverter calToDateConverter)
+ throws DateParseException {
+ return _DateUtil.parseISO8601Date(s, tz, calToDateConverter);
+ }
+
+ @Override
+ protected Date parseTime(String s, TimeZone tz, CalendarFieldsToDateConverter calToDateConverter)
+ throws DateParseException {
+ return _DateUtil.parseISO8601Time(s, tz, calToDateConverter);
+ }
+
+ @Override
+ protected Date parseDateTime(String s, TimeZone tz,
+ CalendarFieldsToDateConverter calToDateConverter) throws DateParseException {
+ return _DateUtil.parseISO8601DateTime(s, tz, calToDateConverter);
+ }
+
+ @Override
+ protected String getDateDescription() {
+ return "ISO 8601 (subset) date";
+ }
+
+ @Override
+ protected String getTimeDescription() {
+ return "ISO 8601 (subset) time";
+ }
+
+ @Override
+ protected String getDateTimeDescription() {
+ return "ISO 8601 (subset) date-time";
+ }
+
+ @Override
+ protected boolean isXSMode() {
+ return false;
+ }
+
+}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/3fd56062/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormatFactory.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormatFactory.java b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormatFactory.java
new file mode 100644
index 0000000..ddace3d
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/valueformat/impl/ISOTemplateDateFormatFactory.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.valueformat.impl;
+
+import java.util.Locale;
+import java.util.TimeZone;
+
+import org.apache.freemarker.core.Environment;
+import org.apache.freemarker.core.valueformat.InvalidFormatParametersException;
+import org.apache.freemarker.core.valueformat.TemplateDateFormat;
+import org.apache.freemarker.core.valueformat.UnknownDateTypeFormattingUnsupportedException;
+
+/**
+ * Creates {@link TemplateDateFormat}-s that follows ISO 8601 extended format that is also compatible with the XML
+ * Schema format (as far as you don't have dates in the BC era). Examples of possible outputs: {@code
+ * "2005-11-27T15:30:00+02:00"}, {@code "2005-11-27"}, {@code "15:30:00Z"}. Note the {@code ":00"} in the time zone
+ * offset; this is not required by ISO 8601, but included for compatibility with the XML Schema format. Regarding the
+ * B.C. issue, those dates will be one year off when read back according the XML Schema format, because of a mismatch
+ * between that format and ISO 8601:2000 Second Edition.
+ */
+public final class ISOTemplateDateFormatFactory extends ISOLikeTemplateDateFormatFactory {
+
+ public static final ISOTemplateDateFormatFactory INSTANCE = new ISOTemplateDateFormatFactory();
+
+ private ISOTemplateDateFormatFactory() {
+ // Not meant to be instantiated
+ }
+
+ @Override
+ public TemplateDateFormat get(String params, int dateType, Locale locale, TimeZone timeZone, boolean zonelessInput,
+ Environment env) throws UnknownDateTypeFormattingUnsupportedException, InvalidFormatParametersException {
+ // We don't cache these as creating them is cheap (only 10% speedup of ${d?string.xs} with caching)
+ return new ISOTemplateDateFormat(
+ params, 3,
+ dateType, zonelessInput,
+ timeZone, this, env);
+ }
+
+}