Skip to content
This repository has been archived by the owner on Jan 8, 2020. It is now read-only.

Commit

Permalink
Added customNumberFormats and customDateFormats configurability from …
Browse files Browse the repository at this point in the history
…Properties
  • Loading branch information
ddekany committed Sep 1, 2015
1 parent 1b429d8 commit ec6f52c
Show file tree
Hide file tree
Showing 4 changed files with 138 additions and 6 deletions.
42 changes: 42 additions & 0 deletions src/main/java/freemarker/core/Configurable.java
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,13 @@ public class Configurable {
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String NUMBER_FORMAT_KEY = NUMBER_FORMAT_KEY_SNAKE_CASE;

/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
public static final String CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE = "custom_number_formats";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
public static final String CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE = "customNumberFormats";
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String CUSTOM_NUMBER_FORMATS_KEY = CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE;

/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
public static final String TIME_FORMAT_KEY_SNAKE_CASE = "time_format";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
Expand All @@ -109,6 +116,13 @@ public class Configurable {
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String DATE_FORMAT_KEY = DATE_FORMAT_KEY_SNAKE_CASE;

/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
public static final String CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE = "custom_date_formats";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
public static final String CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE = "customDateFormats";
/** Alias to the {@code ..._SNAKE_CASE} variation due to backward compatibility constraints. */
public static final String CUSTOM_DATE_FORMATS_KEY = CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE;

/** Legacy, snake case ({@code like_this}) variation of the setting name. @since 2.3.23 */
public static final String DATETIME_FORMAT_KEY_SNAKE_CASE = "datetime_format";
/** Modern, camel case ({@code likeThis}) variation of the setting name. @since 2.3.23 */
Expand Down Expand Up @@ -232,6 +246,8 @@ public class Configurable {
AUTO_FLUSH_KEY_SNAKE_CASE,
BOOLEAN_FORMAT_KEY_SNAKE_CASE,
CLASSIC_COMPATIBLE_KEY_SNAKE_CASE,
CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
DATE_FORMAT_KEY_SNAKE_CASE,
DATETIME_FORMAT_KEY_SNAKE_CASE,
LOCALE_KEY_SNAKE_CASE,
Expand All @@ -256,6 +272,8 @@ public class Configurable {
AUTO_FLUSH_KEY_CAMEL_CASE,
BOOLEAN_FORMAT_KEY_CAMEL_CASE,
CLASSIC_COMPATIBLE_KEY_CAMEL_CASE,
CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
DATE_FORMAT_KEY_CAMEL_CASE,
DATETIME_FORMAT_KEY_CAMEL_CASE,
LOCALE_KEY_CAMEL_CASE,
Expand Down Expand Up @@ -1486,6 +1504,16 @@ public boolean isLogTemplateExceptionsSet() {
* <br>String value: {@code "true"}, {@code "false"}, also since 2.3.20 {@code 0} or {@code 1} or {@code 2}.
* (Also accepts {@code "yes"}, {@code "no"}, {@code "t"}, {@code "f"}, {@code "y"}, {@code "n"}.)
* Case insensitive.
*
* <li><p>{@code "custom_number_formats"}: See {@link #setCustomNumberFormats(Map)}.
* <br>String value: Interpreted as an <a href="#fm_obe">object builder expression</a>.
* <br>Example: <code>{ "hex": com.example.HexTemplateNumberFormatFactory,
* "gps": com.example.GPSTemplateNumberFormatFactory }</code>
*
* <li><p>{@code "custom_date_formats"}: See {@link #setCustomDateFormats(Map)}.
* <br>String value: Interpreted as an <a href="#fm_obe">object builder expression</a>.
* <br>Example: <code>{ "trade": com.example.TradeTemplateDateFormatFactory,
* "log": com.example.LogTemplateDateFormatFactory }</code>
*
* <li><p>{@code "template_exception_handler"}:
* See {@link #setTemplateExceptionHandler(TemplateExceptionHandler)}.
Expand Down Expand Up @@ -1848,12 +1876,26 @@ public void setSetting(String name, String value) throws TemplateException {
setLocale(StringUtil.deduceLocale(value));
} else if (NUMBER_FORMAT_KEY_SNAKE_CASE.equals(name) || NUMBER_FORMAT_KEY_CAMEL_CASE.equals(name)) {
setNumberFormat(value);
} else if (CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE.equals(name)
|| CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE.equals(name)) {
Map map = (Map) _ObjectBuilderSettingEvaluator.eval(
value, Map.class, false, _SettingEvaluationEnvironment.getCurrent());
_CoreAPI.checkSettingValueItemsType("Map keys", String.class, map.keySet());
_CoreAPI.checkSettingValueItemsType("Map values", TemplateNumberFormatFactory.class, map.values());
setCustomNumberFormats(map);
} else if (TIME_FORMAT_KEY_SNAKE_CASE.equals(name) || TIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
setTimeFormat(value);
} else if (DATE_FORMAT_KEY_SNAKE_CASE.equals(name) || DATE_FORMAT_KEY_CAMEL_CASE.equals(name)) {
setDateFormat(value);
} else if (DATETIME_FORMAT_KEY_SNAKE_CASE.equals(name) || DATETIME_FORMAT_KEY_CAMEL_CASE.equals(name)) {
setDateTimeFormat(value);
} else if (CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE.equals(name)
|| CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE.equals(name)) {
Map map = (Map) _ObjectBuilderSettingEvaluator.eval(
value, Map.class, false, _SettingEvaluationEnvironment.getCurrent());
_CoreAPI.checkSettingValueItemsType("Map keys", String.class, map.keySet());
_CoreAPI.checkSettingValueItemsType("Map values", TemplateDateFormatFactory.class, map.values());
setCustomDateFormats(map);
} else if (TIME_ZONE_KEY_SNAKE_CASE.equals(name) || TIME_ZONE_KEY_CAMEL_CASE.equals(name)) {
setTimeZone(parseTimeZoneSettingValue(value));
} else if (SQL_DATE_AND_TIME_TIME_ZONE_KEY_SNAKE_CASE.equals(name)
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/freemarker/core/_CoreAPI.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@
package freemarker.core;

import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.Set;
import java.util.TreeSet;

import freemarker.template.Template;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.utility.ClassUtil;


/**
Expand Down Expand Up @@ -134,5 +136,21 @@ static final public void checkHasNoNestedContent(TemplateDirectiveBody body)
static final public void replaceText(TextBlock textBlock, String text) {
textBlock.replaceText(text);
}

/**
* @throws IllegalArgumentException
* if the type of the some of the values isn't as expected
*/
public static void checkSettingValueItemsType(String somethingsSentenceStart, Class<?> expectedClass,
Collection<? extends Object> values) {
if (values == null) return;
for (Object value : values) {
if (!expectedClass.isInstance(value)) {
throw new IllegalArgumentException(somethingsSentenceStart + " must be instances of "
+ ClassUtil.getShortClassName(expectedClass) + ", but one of them was a(n) "
+ ClassUtil.getShortClassNameOfObject(value) + ".");
}
}
}

}
16 changes: 12 additions & 4 deletions src/manual/book.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25583,11 +25583,12 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
</listitem>

<listitem>
<para>Added
<literal>Configurable.setCustomNumberFormats(Map&lt;String,
<para>Added <literal>custom_number_formats</literal> and
<literal>custom_date_formats</literal> settings
(<literal>Configurable.setCustomNumberFormats(Map&lt;String,
TemplateNumberFormatFactory&gt;)</literal> and
<literal>Configurable.setCustomDateFormats(Map&lt;String,
TemplateDateFormatFactory&gt;)</literal> with which you can
TemplateDateFormatFactory&gt;)</literal>) with which you can
register your own formats. <emphasis>If you <link
linkend="pgui_config_incompatible_improvements_how_to_set">set
<literal>incompatible_improvements</literal></link> to
Expand All @@ -25608,7 +25609,14 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
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.</para>
there literally. Note that the
<literal>custom_number_formats</literal> and
<literal>custom_date_formats</literal> settings can be set
per-template (via the new
<literal>template_configurers</literal> settings) or
per-<literal>Environment</literal> too, thus
<literal>@foo</literal> can mean something different in
different templates.</para>
</listitem>

<listitem>
Expand Down
68 changes: 66 additions & 2 deletions src/test/java/freemarker/template/ConfigurationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
import freemarker.cache.TemplateLookupResult;
import freemarker.cache.TemplateLookupStrategy;
import freemarker.cache.TemplateNameFormat;
import freemarker.core.BaseNTemplateNumberFormatFactory;
import freemarker.core.CombinedMarkupOutputFormat;
import freemarker.core.Configurable;
import freemarker.core.Configurable.SettingValueAssignmentException;
Expand All @@ -56,13 +57,16 @@
import freemarker.core.CustomHTMLOutputFormat;
import freemarker.core.DummyOutputFormat;
import freemarker.core.Environment;
import freemarker.core.EpochMillisDivTemplateDateFormatFactory;
import freemarker.core.EpochMillisTemplateDateFormatFactory;
import freemarker.core.HTMLOutputFormat;
import freemarker.core.HexTemplateNumberFormatFactory;
import freemarker.core.MarkupOutputFormat;
import freemarker.core.OutputFormat;
import freemarker.core.ParseException;
import freemarker.core.RTFOutputFormat;
import freemarker.core.TemplateDateFormatFactory;
import freemarker.core.TemplateNumberFormatFactory;
import freemarker.core.UndefinedOutputFormat;
import freemarker.core.UnregisteredOutputFormatException;
import freemarker.core.XMLOutputFormat;
Expand Down Expand Up @@ -1298,7 +1302,7 @@ public void testTemplateUpdateDelay() throws IOException, TemplateException {
}

@Test
public void testSetCustomNumberFormat() {
public void testSetCustomNumberFormat() throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);

try {
Expand Down Expand Up @@ -1337,10 +1341,40 @@ public void testSetCustomNumberFormat() {
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("@wrong"));
}

cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
"{ 'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "() }");
assertEquals(
Collections.singletonMap("base", BaseNTemplateNumberFormatFactory.INSTANCE),
cfg.getCustomNumberFormats());

cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE,
"{ "
+ "'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "(), "
+ "'hex': " + HexTemplateNumberFormatFactory.class.getName() + "()"
+ " }");
assertEquals(
ImmutableMap.of(
"base", BaseNTemplateNumberFormatFactory.INSTANCE,
"hex", HexTemplateNumberFormatFactory.INSTANCE),
cfg.getCustomNumberFormats());

cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY, "{}");
assertEquals(Collections.emptyMap(), cfg.getCustomNumberFormats());

try {
cfg.setSetting(Configurable.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE,
"{ 'x': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }");
fail();
} catch (TemplateException e) {
assertThat(e.getCause().getMessage(), allOf(
containsString(EpochMillisTemplateDateFormatFactory.class.getName()),
containsString(TemplateNumberFormatFactory.class.getName())));
}
}

@Test
public void testSetCustomDateFormat() {
public void testSetCustomDateFormat() throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_0);

try {
Expand Down Expand Up @@ -1379,6 +1413,36 @@ public void testSetCustomDateFormat() {
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("@wrong"));
}

cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
"{ 'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }");
assertEquals(
Collections.singletonMap("epoch", EpochMillisTemplateDateFormatFactory.INSTANCE),
cfg.getCustomDateFormats());

cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE,
"{ "
+ "'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "(), "
+ "'epochDiv': " + EpochMillisDivTemplateDateFormatFactory.class.getName() + "()"
+ " }");
assertEquals(
ImmutableMap.of(
"epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
"epochDiv", EpochMillisDivTemplateDateFormatFactory.INSTANCE),
cfg.getCustomDateFormats());

cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY, "{}");
assertEquals(Collections.emptyMap(), cfg.getCustomDateFormats());

try {
cfg.setSetting(Configurable.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE,
"{ 'x': " + HexTemplateNumberFormatFactory.class.getName() + "() }");
fail();
} catch (TemplateException e) {
assertThat(e.getCause().getMessage(), allOf(
containsString(HexTemplateNumberFormatFactory.class.getName()),
containsString(TemplateDateFormatFactory.class.getName())));
}
}

public void testNamingConventionSetSetting() throws TemplateException {
Expand Down

0 comments on commit ec6f52c

Please sign in to comment.