blob: e7c1ab1ca5250d6d5aa97f6d40077ac5177261cf [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.freemarker.core;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import org.apache.freemarker.core.cformat.impl.XSCFormat;
import org.apache.freemarker.core.model.TemplateHashModel;
import org.apache.freemarker.core.model.TemplateStringModel;
import org.apache.freemarker.core.model.impl.*;
import org.apache.freemarker.core.outputformat.MarkupOutputFormat;
import org.apache.freemarker.core.outputformat.OutputFormat;
import org.apache.freemarker.core.outputformat.UnregisteredOutputFormatException;
import org.apache.freemarker.core.outputformat.impl.CombinedMarkupOutputFormat;
import org.apache.freemarker.core.outputformat.impl.HTMLOutputFormat;
import org.apache.freemarker.core.outputformat.impl.RTFOutputFormat;
import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat;
import org.apache.freemarker.core.templateresolver.*;
import org.apache.freemarker.core.templateresolver.impl.*;
import org.apache.freemarker.core.userpkg.*;
import org.apache.freemarker.core.util._CollectionUtils;
import org.apache.freemarker.core.util._DateUtils;
import org.apache.freemarker.core.util._NullWriter;
import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory;
import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory;
import org.junit.Test;
import java.io.File;
import java.io.IOException;
import java.io.Serializable;
import java.io.StringWriter;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.*;
import static org.apache.freemarker.core.Configuration.*;
import static org.apache.freemarker.core.Configuration.ExtendableBuilder.*;
import static org.apache.freemarker.test.hamcerst.Matchers.containsStringIgnoringCase;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class ConfigurationTest {
private static final Charset ISO_8859_2 = Charset.forName("ISO-8859-2");
@Test
public void testUnsetAndIsSet() throws Exception {
// TODO This should automatically test all setting via reflection...
Configuration.ExtendableBuilder<?> cfgB = new Builder(VERSION_3_0_0);
assertFalse(cfgB.isAutoEscapingPolicySet());
assertEquals(AutoEscapingPolicy.ENABLE_IF_DEFAULT, cfgB.getAutoEscapingPolicy());
//
cfgB.setAutoEscapingPolicy(AutoEscapingPolicy.DISABLE);
{
assertTrue(cfgB.isAutoEscapingPolicySet());
assertEquals(AutoEscapingPolicy.DISABLE, cfgB.getAutoEscapingPolicy());
}
//
for (int i = 0; i < 2; i++) {
cfgB.unsetAutoEscapingPolicy();
assertFalse(cfgB.isAutoEscapingPolicySet());
assertEquals(AutoEscapingPolicy.ENABLE_IF_DEFAULT, cfgB.getAutoEscapingPolicy());
}
DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(VERSION_3_0_0).build();
assertFalse(cfgB.isObjectWrapperSet());
assertSame(dow, cfgB.getObjectWrapper());
//
RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(VERSION_3_0_0).build();
cfgB.setObjectWrapper(ow);
assertTrue(cfgB.isObjectWrapperSet());
assertSame(ow, cfgB.getObjectWrapper());
//
for (int i = 0; i < 2; i++) {
cfgB.unsetObjectWrapper();
assertFalse(cfgB.isObjectWrapperSet());
assertSame(dow, cfgB.getObjectWrapper());
}
assertFalse(cfgB.isTemplateExceptionHandlerSet());
assertSame(TemplateExceptionHandler.RETHROW, cfgB.getTemplateExceptionHandler());
//
cfgB.setTemplateExceptionHandler(TemplateExceptionHandler.DEBUG);
assertTrue(cfgB.isTemplateExceptionHandlerSet());
assertSame(TemplateExceptionHandler.DEBUG, cfgB.getTemplateExceptionHandler());
//
for (int i = 0; i < 2; i++) {
cfgB.unsetTemplateExceptionHandler();
assertFalse(cfgB.isTemplateExceptionHandlerSet());
assertSame(TemplateExceptionHandler.RETHROW, cfgB.getTemplateExceptionHandler());
}
assertFalse(cfgB.isTemplateLoaderSet());
assertNull(cfgB.getTemplateLoader());
//
cfgB.setTemplateLoader(null);
assertTrue(cfgB.isTemplateLoaderSet());
assertNull(cfgB.getTemplateLoader());
//
for (int i = 0; i < 3; i++) {
if (i == 2) {
cfgB.setTemplateLoader(new StringTemplateLoader());
}
cfgB.unsetTemplateLoader();
assertFalse(cfgB.isTemplateLoaderSet());
assertNull(cfgB.getTemplateLoader());
}
assertFalse(cfgB.isTemplateLookupStrategySet());
assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.getTemplateLookupStrategy());
//
cfgB.setTemplateLookupStrategy(DefaultTemplateLookupStrategy.INSTANCE);
assertTrue(cfgB.isTemplateLookupStrategySet());
//
for (int i = 0; i < 2; i++) {
cfgB.unsetTemplateLookupStrategy();
assertFalse(cfgB.isTemplateLookupStrategySet());
}
assertFalse(cfgB.isTemplateNameFormatSet());
assertSame(DefaultTemplateNameFormat.INSTANCE, cfgB.getTemplateNameFormat());
//
cfgB.setTemplateNameFormat(null);
assertTrue(cfgB.isTemplateNameFormatSet());
assertNull(cfgB.getTemplateNameFormat());
//
for (int i = 0; i < 2; i++) {
cfgB.unsetTemplateNameFormat();
assertFalse(cfgB.isTemplateNameFormatSet());
assertSame(DefaultTemplateNameFormat.INSTANCE, cfgB.getTemplateNameFormat());
}
assertFalse(cfgB.isTemplateCacheStorageSet());
assertTrue(cfgB.getTemplateCacheStorage() instanceof SoftCacheStorage);
//
cfgB.setTemplateCacheStorage(NullCacheStorage.INSTANCE);
assertTrue(cfgB.isTemplateCacheStorageSet());
assertSame(NullCacheStorage.INSTANCE, cfgB.getTemplateCacheStorage());
//
for (int i = 0; i < 3; i++) {
if (i == 2) {
cfgB.setTemplateCacheStorage(cfgB.getTemplateCacheStorage());
}
cfgB.unsetTemplateCacheStorage();
assertFalse(cfgB.isTemplateCacheStorageSet());
assertTrue(cfgB.getTemplateCacheStorage() instanceof SoftCacheStorage);
}
assertFalse(cfgB.isCFormatSet());
//
cfgB.setCFormat(XSCFormat.INSTANCE);
assertTrue(cfgB.isCFormatSet());
//
cfgB.unsetCFormat();
assertFalse(cfgB.isCFormatSet());
}
@Test
public void testTemplateLoadingErrors() throws Exception {
Configuration cfg = new Builder(VERSION_3_0_0)
.templateLoader(new ClassTemplateLoader(getClass(), "nosuchpackage"))
.build();
try {
cfg.getTemplate("missing.f3ah");
fail();
} catch (TemplateNotFoundException e) {
assertThat(e.getMessage(), not(containsString("wasn't set")));
}
}
@Test
public void testVersion() {
Version v = getVersion();
assertTrue(v.intValue() >= _VersionInts.VERSION_INT_3_0_0);
try {
new Builder(new Version(999, 1, 2)).build();
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("upgrade"));
}
try {
new Builder(new Version(2, 3, 0)).build();
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("3.0.0"));
}
try {
new Builder(Configuration.getVersion()).build();
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("getVersion()"));
}
new Builder(new Version(Configuration.getVersion().toString()));
}
@Test
public void testShowErrorTips() throws Exception {
try {
Configuration cfg = new Builder(VERSION_3_0_0).build();
new Template(null, "${x}", cfg).process(null, _NullWriter.INSTANCE);
fail();
} catch (TemplateException e) {
assertThat(e.getMessage(), containsString("Tip:"));
}
try {
Configuration cfg = new Builder(VERSION_3_0_0).showErrorTips(false).build();
new Template(null, "${x}", cfg).process(null, _NullWriter.INSTANCE);
fail();
} catch (TemplateException e) {
assertThat(e.getMessage(), not(containsString("Tip:")));
}
}
@Test
@SuppressWarnings("boxing")
public void testGetTemplateOverloads() throws Exception {
final Locale hu = new Locale("hu", "HU");
final String tFtl = "t.f3ah";
final String tHuFtl = "t_hu.f3ah";
final String tEnFtl = "t_en.f3ah";
final String tUtf8Ftl = "utf8.f3ah";
final Serializable custLookupCond = 12345;
ByteArrayTemplateLoader tl = new ByteArrayTemplateLoader();
tl.putTemplate(tFtl, "${1}".getBytes(StandardCharsets.UTF_8));
tl.putTemplate(tEnFtl, "${1}".getBytes(StandardCharsets.UTF_8));
tl.putTemplate(tHuFtl, "${1}".getBytes(StandardCharsets.UTF_8));
tl.putTemplate(tUtf8Ftl, "<#ftl encoding='utf-8'>".getBytes(StandardCharsets.UTF_8));
Configuration cfg = new Builder(VERSION_3_0_0)
.locale(Locale.GERMAN)
.sourceEncoding(StandardCharsets.ISO_8859_1)
.templateLoader(tl)
.templateConfigurations(
new ConditionalTemplateConfigurationFactory(
new FileNameGlobMatcher("*_hu.*"),
new TemplateConfiguration.Builder().sourceEncoding(ISO_8859_2).build()))
.build();
// 1 args:
{
Template t = cfg.getTemplate(tFtl);
assertEquals(tFtl, t.getLookupName());
assertEquals(tFtl, t.getSourceName());
assertEquals(Locale.GERMAN, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding());
}
{
Template t = cfg.getTemplate(tUtf8Ftl);
assertEquals(tUtf8Ftl, t.getLookupName());
assertEquals(tUtf8Ftl, t.getSourceName());
assertEquals(Locale.GERMAN, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
}
// 2 args:
{
Template t = cfg.getTemplate(tFtl, Locale.GERMAN);
assertEquals(tFtl, t.getLookupName());
assertEquals(tFtl, t.getSourceName());
assertEquals(Locale.GERMAN, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding());
}
{
Template t = cfg.getTemplate(tFtl, null);
assertEquals(tFtl, t.getLookupName());
assertEquals(tFtl, t.getSourceName());
assertEquals(Locale.GERMAN, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding());
}
{
Template t = cfg.getTemplate(tFtl, Locale.US);
assertEquals(tFtl, t.getLookupName());
assertEquals(tEnFtl, t.getSourceName());
assertEquals(Locale.US, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding());
}
{
Template t = cfg.getTemplate(tUtf8Ftl, Locale.US);
assertEquals(tUtf8Ftl, t.getLookupName());
assertEquals(tUtf8Ftl, t.getSourceName());
assertEquals(Locale.US, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
}
{
Template t = cfg.getTemplate(tFtl, hu);
assertEquals(tFtl, t.getLookupName());
assertEquals(tHuFtl, t.getSourceName());
assertEquals(hu, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(ISO_8859_2, t.getActualSourceEncoding());
}
{
Template t = cfg.getTemplate(tUtf8Ftl, hu);
assertEquals(tUtf8Ftl, t.getLookupName());
assertEquals(tUtf8Ftl, t.getSourceName());
assertEquals(hu, t.getLocale());
assertNull(t.getCustomLookupCondition());
assertEquals(StandardCharsets.UTF_8, t.getActualSourceEncoding());
}
// 4 args:
try {
cfg.getTemplate("missing.f3ah", hu, custLookupCond, false);
fail();
} catch (TemplateNotFoundException e) {
// Expected
}
assertNull(cfg.getTemplate("missing.f3ah", hu, custLookupCond, true));
{
Template t = cfg.getTemplate(tFtl, hu, custLookupCond, false);
assertEquals(tFtl, t.getLookupName());
assertEquals(tHuFtl, t.getSourceName());
assertEquals(hu, t.getLocale());
assertEquals(custLookupCond, t.getCustomLookupCondition());
assertEquals(ISO_8859_2, t.getActualSourceEncoding());
assertOutputEquals("1", t);
}
{
Template t = cfg.getTemplate(tFtl, null, custLookupCond, false);
assertEquals(tFtl, t.getLookupName());
assertEquals(tFtl, t.getSourceName());
assertEquals(Locale.GERMAN, t.getLocale());
assertEquals(custLookupCond, t.getCustomLookupCondition());
assertEquals(StandardCharsets.ISO_8859_1, t.getActualSourceEncoding());
assertOutputEquals("1", t);
}
}
private void assertOutputEquals(final String expectedContent, final Template t) throws ConfigurationException,
IOException, TemplateException {
StringWriter sw = new StringWriter();
t.process(null, sw);
assertEquals(expectedContent, sw.toString());
}
@Test
public void testTemplateResolverCache() throws Exception {
Builder cfgB = new Builder(VERSION_3_0_0);
CacheStorageWithGetSize cache = (CacheStorageWithGetSize) cfgB.getTemplateCacheStorage();
assertEquals(0, cache.getSize());
cfgB.setTemplateCacheStorage(new StrongCacheStorage());
cache = (CacheStorageWithGetSize) cfgB.getTemplateCacheStorage();
assertEquals(0, cache.getSize());
cfgB.setTemplateLoader(new ClassTemplateLoader(ConfigurationTest.class, ""));
Configuration cfg = cfgB.build();
assertEquals(0, cache.getSize());
cfg.getTemplate("toCache1.f3ah");
assertEquals(1, cache.getSize());
cfg.getTemplate("toCache2.f3ah");
assertEquals(2, cache.getSize());
cfg.clearTemplateCache();
assertEquals(0, cache.getSize());
cfg.getTemplate("toCache1.f3ah");
assertEquals(1, cache.getSize());
cfgB.setTemplateLoader(cfgB.getTemplateLoader());
assertEquals(1, cache.getSize());
}
@Test
public void testTemplateNameFormat() throws Exception {
StringTemplateLoader tl = new StringTemplateLoader();
tl.putTemplate("a/b.f3ah", "In a/b.f3ah");
tl.putTemplate("b.f3ah", "In b.f3ah");
{
Configuration cfg = new Builder(VERSION_3_0_0)
.templateLoader(tl)
.templateNameFormat(DefaultTemplateNameFormat.INSTANCE)
.build();
final Template template = cfg.getTemplate("a/./../b.f3ah");
assertEquals("b.f3ah", template.getLookupName());
assertEquals("b.f3ah", template.getSourceName());
assertEquals("In b.f3ah", template.toString());
}
}
@Test
public void testTemplateNameFormatSetSetting() throws Exception {
Builder cfgB = new Builder(VERSION_3_0_0);
assertSame(DefaultTemplateNameFormat.INSTANCE, cfgB.getTemplateNameFormat());
cfgB.setTemplateNameFormat(null);
assertNull(cfgB.getTemplateNameFormat());
cfgB.setSetting(TEMPLATE_NAME_FORMAT_KEY, "defauLt");
assertFalse(cfgB.isTemplateNameFormatSet());
}
@Test
public void testObjectWrapperSetSetting() throws Exception {
Builder cfgB = new Builder(VERSION_3_0_0);
{
cfgB.setSetting(OBJECT_WRAPPER_KEY, "defAult");
DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(VERSION_3_0_0).build();
assertSame(dow, cfgB.getObjectWrapper());
assertEquals(VERSION_3_0_0, dow.getIncompatibleImprovements());
}
{
cfgB.setSetting(OBJECT_WRAPPER_KEY, "restricted");
assertThat(cfgB.getObjectWrapper(), instanceOf(RestrictedObjectWrapper.class));
}
}
@Test
public void testTemplateLookupStrategyDefault() throws Exception {
Configuration cfg = new Builder(VERSION_3_0_0)
.templateLoader(new ClassTemplateLoader(ConfigurationTest.class, ""))
.build();
assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfg.getTemplateLookupStrategy());
assertEquals("toCache1.f3ah", cfg.getTemplate("toCache1.f3ah").getSourceName());
}
@Test
public void testTemplateLookupStrategyCustom() throws Exception {
final TemplateLookupStrategy myStrategy = new TemplateLookupStrategy() {
@Override
public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException {
return ctx.lookupWithAcquisitionStrategy("toCache2.f3ah");
}
};
Configuration cfg = new Builder(VERSION_3_0_0)
.templateLoader(new ClassTemplateLoader(ConfigurationTest.class, ""))
.templateLookupStrategy(myStrategy)
.build();
assertSame(myStrategy, cfg.getTemplateLookupStrategy());
assertEquals("toCache2.f3ah", cfg.getTemplate("toCache1.f3ah").getSourceName());
}
@Test
public void testSetTemplateConfigurations() throws Exception {
Builder cfgB = new Builder(VERSION_3_0_0);
assertNull(cfgB.getTemplateConfigurations());
StringTemplateLoader tl = new StringTemplateLoader();
tl.putTemplate("t.de.f3ah", "");
tl.putTemplate("t.fr.f3ax", "");
tl.putTemplate("t.f3ax", "");
tl.putTemplate("Stat/t.de.f3ax", "");
cfgB.setTemplateLoader(tl);
cfgB.setTimeZone(TimeZone.getTimeZone("GMT+09"));
cfgB.setSetting(TEMPLATE_CONFIGURATIONS_KEY,
"MergingTemplateConfigurationFactory("
+ "FirstMatchTemplateConfigurationFactory("
+ "ConditionalTemplateConfigurationFactory("
+ "FileNameGlobMatcher('*.de.*'), TemplateConfiguration(timeZone=TimeZone('GMT+01'))), "
+ "ConditionalTemplateConfigurationFactory("
+ "FileNameGlobMatcher('*.fr.*'), TemplateConfiguration(timeZone=TimeZone('GMT'))), "
+ "allowNoMatch=true"
+ "), "
+ "FirstMatchTemplateConfigurationFactory("
+ "ConditionalTemplateConfigurationFactory("
+ "FileExtensionMatcher('f3ah'), TemplateConfiguration(booleanFormat='TODO,HTML')), "
+ "ConditionalTemplateConfigurationFactory("
+ "FileExtensionMatcher('f3ax'), TemplateConfiguration(booleanFormat='TODO,XML')), "
+ "noMatchErrorDetails='Unrecognized template file extension'"
+ "), "
+ "ConditionalTemplateConfigurationFactory("
+ "PathGlobMatcher('stat/**', caseInsensitive=true), "
+ "TemplateConfiguration(timeZone=TimeZone('UTC'))"
+ ")"
+ ")");
Configuration cfg = cfgB.build();
{
Template t = cfg.getTemplate("t.de.f3ah");
assertEquals("TODO,HTML", t.getBooleanFormat());
assertEquals(TimeZone.getTimeZone("GMT+01"), t.getTimeZone());
}
{
Template t = cfg.getTemplate("t.fr.f3ax");
assertEquals("TODO,XML", t.getBooleanFormat());
assertEquals(TimeZone.getTimeZone("GMT"), t.getTimeZone());
}
{
Template t = cfg.getTemplate("t.f3ax");
assertEquals("TODO,XML", t.getBooleanFormat());
assertEquals(TimeZone.getTimeZone("GMT+09"), t.getTimeZone());
}
{
Template t = cfg.getTemplate("Stat/t.de.f3ax");
assertEquals("TODO,XML", t.getBooleanFormat());
assertEquals(_DateUtils.UTC, t.getTimeZone());
}
assertNotNull(cfgB.getTemplateConfigurations());
cfgB.setSetting(TEMPLATE_CONFIGURATIONS_KEY, "null");
assertNull(cfgB.getTemplateConfigurations());
}
@Test
public void testGetOutputFormatByName() throws Exception {
Configuration cfg = new Builder(VERSION_3_0_0).build();
assertSame(HTMLOutputFormat.INSTANCE, cfg.getOutputFormat(HTMLOutputFormat.INSTANCE.getName()));
try {
cfg.getOutputFormat("noSuchFormat");
fail();
} catch (UnregisteredOutputFormatException e) {
assertThat(e.getMessage(), containsString("noSuchFormat"));
}
try {
cfg.getOutputFormat("HTML}");
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), containsString("'{'"));
}
{
OutputFormat of = cfg.getOutputFormat("HTML{RTF}");
assertThat(of, instanceOf(CombinedMarkupOutputFormat.class));
CombinedMarkupOutputFormat combinedOF = (CombinedMarkupOutputFormat) of;
assertSame(HTMLOutputFormat.INSTANCE, combinedOF.getOuterOutputFormat());
assertSame(RTFOutputFormat.INSTANCE, combinedOF.getInnerOutputFormat());
}
{
OutputFormat of = cfg.getOutputFormat("XML{HTML{RTF}}");
assertThat(of, instanceOf(CombinedMarkupOutputFormat.class));
CombinedMarkupOutputFormat combinedOF = (CombinedMarkupOutputFormat) of;
assertSame(XMLOutputFormat.INSTANCE, combinedOF.getOuterOutputFormat());
MarkupOutputFormat innerOF = combinedOF.getInnerOutputFormat();
assertThat(innerOF, instanceOf(CombinedMarkupOutputFormat.class));
CombinedMarkupOutputFormat innerCombinedOF = (CombinedMarkupOutputFormat) innerOF;
assertSame(HTMLOutputFormat.INSTANCE, innerCombinedOF.getOuterOutputFormat());
assertSame(RTFOutputFormat.INSTANCE, innerCombinedOF.getInnerOutputFormat());
}
try {
cfg.getOutputFormat("plainText{HTML}");
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), allOf(containsString("plainText"), containsString("markup")));
}
try {
cfg.getOutputFormat("HTML{plainText}");
fail();
} catch (IllegalArgumentException e) {
assertThat(e.getMessage(), allOf(containsString("plainText"), containsString("markup")));
}
}
@Test
public void testSetRegisteredCustomOutputFormats() throws Exception {
Builder cfg = new Builder(VERSION_3_0_0);
assertTrue(cfg.getRegisteredCustomOutputFormats().isEmpty());
cfg.setSetting(REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY,
"[org.apache.freemarker.core.userpkg.CustomHTMLOutputFormat(), "
+ "org.apache.freemarker.core.userpkg.DummyOutputFormat()]");
assertEquals(
ImmutableList.of(CustomHTMLOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE),
new ArrayList(cfg.getRegisteredCustomOutputFormats()));
try {
cfg.setSetting(REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY, "[TemplateConfiguration()]");
fail();
} catch (InvalidSettingValueException e) {
assertThat(e.getMessage(), containsString(OutputFormat.class.getSimpleName()));
}
}
@Test
public void testSetICIViaSetSettingAPI() throws ConfigurationException {
Builder cfgB = new Builder(VERSION_3_0_0);
assertEquals(DEFAULT_INCOMPATIBLE_IMPROVEMENTS, cfgB.getIncompatibleImprovements());
// This is the only valid value ATM:
cfgB.setSetting(MutableParsingAndProcessingConfiguration.INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0");
assertEquals(VERSION_3_0_0, cfgB.getIncompatibleImprovements());
}
@Test
public void testSetAttemptExceptionReporter() throws TemplateException {
Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0);
assertEquals(AttemptExceptionReporter.LOG_ERROR, cfgB.getAttemptExceptionReporter());
assertFalse(cfgB.isAttemptExceptionReporterSet());
cfgB.setSetting(MutableProcessingConfiguration.ATTEMPT_EXCEPTION_REPORTER_KEY, "logWarn");
assertEquals(AttemptExceptionReporter.LOG_WARN, cfgB.getAttemptExceptionReporter());
assertTrue(cfgB.isAttemptExceptionReporterSet());
cfgB.setSetting(MutableProcessingConfiguration.ATTEMPT_EXCEPTION_REPORTER_KEY, "default");
assertEquals(AttemptExceptionReporter.LOG_ERROR, cfgB.getAttemptExceptionReporter());
assertFalse(cfgB.isAttemptExceptionReporterSet());
assertEquals(AttemptExceptionReporter.LOG_ERROR,
new Configuration.Builder(Configuration.VERSION_3_0_0)
.build()
.getAttemptExceptionReporter());
assertEquals(AttemptExceptionReporter.LOG_WARN,
new Configuration.Builder(Configuration.VERSION_3_0_0)
.attemptExceptionReporter(AttemptExceptionReporter.LOG_WARN)
.build()
.getAttemptExceptionReporter());
}
@Test
public void testSharedVariables() throws TemplateException, IOException {
Configuration cfg = new Builder(VERSION_3_0_0)
.sharedVariables(ImmutableMap.of(
"a", "aa",
"b", "bb",
"c", new MyStringModel()
))
.build();
assertNull(cfg.getSharedVariables().get("noSuchVar"));
assertNull(cfg.getWrappedSharedVariable("noSuchVar"));
TemplateStringModel aVal = (TemplateStringModel) cfg.getWrappedSharedVariable("a");
assertEquals("aa", aVal.getAsString());
assertEquals(SimpleString.class, aVal.getClass());
TemplateStringModel bVal = (TemplateStringModel) cfg.getWrappedSharedVariable("b");
assertEquals("bb", bVal.getAsString());
assertEquals(SimpleString.class, bVal.getClass());
TemplateStringModel cVal = (TemplateStringModel) cfg.getWrappedSharedVariable("c");
assertEquals("my", cVal.getAsString());
assertEquals(MyStringModel.class, cfg.getWrappedSharedVariable("c").getClass());
// See if it actually works in templates:
StringWriter sw = new StringWriter();
new Template(null, "${a} ${b}", cfg)
.process(ImmutableMap.of("a", "aaDM"), sw);
assertEquals("aaDM bb", sw.toString());
}
@Test
public void testTemplateUpdateDelay() throws Exception {
Builder cfgB = new Builder(VERSION_3_0_0);
assertEquals(
DefaultTemplateResolver.DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS,
(Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setTemplateUpdateDelayMilliseconds(4000L);
assertEquals(4000L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setTemplateUpdateDelayMilliseconds(100L);
assertEquals(100L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
try {
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "5");
assertEquals(5000L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
} catch (InvalidSettingValueException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("unit must be specified"));
}
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "0");
assertEquals(0L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
try {
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "5 foo");
assertEquals(5000L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
} catch (InvalidSettingValueException e) {
assertThat(e.getMessage(), containsStringIgnoringCase("\"foo\""));
}
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "3 ms");
assertEquals(3L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "4ms");
assertEquals(4L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "3 s");
assertEquals(3000L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "4s");
assertEquals(4000L, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "3 m");
assertEquals(1000L * 60 * 3, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "4m");
assertEquals(1000L * 60 * 4, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "1 h");
assertEquals(1000L * 60 * 60, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
cfgB.setSetting(TEMPLATE_UPDATE_DELAY_KEY, "2h");
assertEquals(1000L * 60 * 60 * 2, (Object) cfgB.getTemplateUpdateDelayMilliseconds());
}
public static final MemberAccessPolicy CONFIG_TEST_MEMBER_ACCESS_POLICY;
static {
try {
CONFIG_TEST_MEMBER_ACCESS_POLICY = new WhitelistMemberAccessPolicy(MemberSelectorListMemberAccessPolicy.MemberSelector.parse(
ImmutableList.of(
File.class.getName() + ".getName()",
File.class.getName() + ".isFile()"),
false,
ConfigurationTest.class.getClassLoader()));
} catch (ClassNotFoundException | NoSuchMethodException | NoSuchFieldException e) {
throw new RuntimeException(e);
}
}
@Test
public void testMemberAccessPolicySetting() throws TemplateException {
Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0)
.setting(
"objectWrapper",
"DefaultObjectWrapper(3.0.0, "
+ "memberAccessPolicy="
+ ConfigurationTest.class.getName() + ".CONFIG_TEST_MEMBER_ACCESS_POLICY"
+ ")")
.build();
TemplateHashModel m = (TemplateHashModel) cfg.getObjectWrapper().wrap(new File("x"));
assertNotNull(m.get("getName"));
assertNotNull(m.get("isFile"));
assertNull(m.get("delete"));
}
@Test
public void testGetSettingNamesAreSorted() throws Exception {
List<String> names = new ArrayList<>(Builder.getSettingNames());
List<String> inheritedNames = new ArrayList<>(
MutableParsingAndProcessingConfiguration.getSettingNames());
assertStartsWith(names, inheritedNames);
String prevName = null;
for (int i = inheritedNames.size(); i < names.size(); i++) {
String name = names.get(i);
if (prevName != null) {
assertThat(name, greaterThan(prevName));
}
prevName = name;
}
}
@Test
public void testAllSettingsAreCoveredByMutableSettingsObject() throws Exception {
testAllSettingsAreCoveredByMutableSettingsObject(TopLevelConfiguration.class, Configuration.Builder.class);
}
@SuppressWarnings("rawtypes")
public static void testAllSettingsAreCoveredByMutableSettingsObject(
Class<?> settingsInterface, Class<?> settingsMutableObjectClass) throws Exception {
Set<String> beanSettingNames = new TreeSet<>();
Set<String> isSetSettingNames = new TreeSet<>();
for (Method method : settingsInterface.getMethods()) {
String name = method.getName();
if (method.getParameterTypes().length == 0 && method.getReturnType() != void.class
&& (method.getModifiers() & Modifier.PUBLIC) != 0) {
if (name.startsWith("is") && name.endsWith("Set") && method.getReturnType() == boolean.class) {
isSetSettingNames.add(toGetterNameToSettingNameCase(name.substring(2, name.length() - 3)));
} else if (name.startsWith("get")) {
String settingName = toGetterNameToSettingNameCase(name.substring(3));
beanSettingNames.add(settingName);
}
}
}
assertEquals("Not all getXxx have isXxxSet pair", beanSettingNames, isSetSettingNames);
Method method = settingsMutableObjectClass.getMethod("getSettingNames");
assertTrue((method.getModifiers() & Modifier.STATIC) != 0);
assertEquals("Names deduced from getXxx methods and getSettingNames() result differs",
beanSettingNames, new TreeSet<>((Set) method.invoke(null)));
// TODO [FM3] Check if all has setXxx, unsetXxx, etc.
}
private static String toGetterNameToSettingNameCase(String name) {
int idx = 0;
while (idx < name.length() && Character.isUpperCase(name.charAt(idx))) {
idx++;
}
String result =
idx == 0 ? name
: idx == 1 ? Character.toLowerCase(name.charAt(0)) + name.substring(1)
: name.substring(0, idx - 1).toLowerCase() + name.substring(idx - 1); // "FOOBar" -> "fooBar"
if (result.equals("templateUpdateDelayMilliseconds")) {
result = "templateUpdateDelay";
}
return result;
}
@Test
public void testGetSettingNamesCorrespondToStaticKeyFields() throws Exception {
testGetSettingNamesCorrespondToStaticKeyFields(
Configuration.Builder.getSettingNames(),
Configuration.Builder.class);
}
public static void testGetSettingNamesCorrespondToStaticKeyFields(Set<String> names, Class<?> cfgClass) throws
Exception {
Set<String> uncoveredNames = new HashSet<>(names);
for (Field f : cfgClass.getFields()) {
if (f.getName().endsWith("_KEY")) {
final String name = (String) f.get(null);
assertTrue("Missing setting name: " + name, names.contains(name));
uncoveredNames.remove(name);
}
}
assertEquals("Some setting names aren't covered by the ..._KEY constants.",
Collections.emptySet(), uncoveredNames);
}
@Test
public void testRemovedSettings() {
Builder cfgB = new Builder(VERSION_3_0_0);
try {
cfgB.setSetting("classic_compatible", "true");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), allOf(containsString("removed"), containsString("3.0.0")));
}
try {
cfgB.setSetting("strict_syntax", "true");
fail();
} catch (ConfigurationException e) {
assertThat(e.getMessage(), allOf(containsString("removed"), containsString("3.0.0")));
}
}
@Test
public void testCanBeBuiltOnlyOnce() {
Builder builder = new Builder(VERSION_3_0_0);
builder.build();
try {
builder.build();
fail();
} catch (IllegalStateException e) {
// Expected
}
}
@Test
public void testCollectionSettingMutability() throws IOException {
Builder cb = new Builder(VERSION_3_0_0);
assertTrue(_CollectionUtils.isMapKnownToBeUnmodifiable(cb.getSharedVariables()));
Map<String, Object> mutableValue = new HashMap<>();
mutableValue.put("x", "v1");
cb.setSharedVariables(mutableValue);
Map<String, Object> immutableValue = cb.getSharedVariables();
assertNotSame(mutableValue, immutableValue); // Must be a copy
assertTrue(_CollectionUtils.isMapKnownToBeUnmodifiable(immutableValue));
assertEquals(mutableValue, immutableValue);
mutableValue.put("y", "v2");
assertNotEquals(mutableValue, immutableValue); // No aliasing
}
@Test
public void testImpliedSettingValues()
throws IOException, TemplateConfigurationFactoryException, UnregisteredOutputFormatException {
Configuration cfg = new ImpliedSettingValuesTestBuilder().build();
assertEquals("Y,N", cfg.getTemplateConfigurations().get("t.yn", null).getBooleanFormat());
assertNotNull(cfg.getCustomNumberFormat("hex"));
assertNotNull(cfg.getCustomDateFormat("epoch"));
assertEquals(ImmutableMap.of("lib", "lib.f3ah"), cfg.getAutoImports());
assertEquals(ImmutableList.of("inc.f3ah"), cfg.getAutoIncludes());
assertEquals(ImmutableMap.of("v", 1), cfg.getSharedVariables());
assertEquals(ImmutableList.of(CustomHTMLOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE),
cfg.getRegisteredCustomOutputFormats());
assertSame(CustomHTMLOutputFormat.INSTANCE, cfg.getOutputFormat("HTML"));
assertSame(DummyOutputFormat.INSTANCE, cfg.getOutputFormat("dummy"));
assertSame(XMLOutputFormat.INSTANCE, cfg.getOutputFormat("XML"));
}
@Test
public void testImpliedSettingValues2()
throws IOException, TemplateConfigurationFactoryException, UnregisteredOutputFormatException {
Configuration cfg = new ImpliedSettingValuesTestBuilder()
.templateConfigurations(
new ConditionalTemplateConfigurationFactory(
new FileNameGlobMatcher("*.jn"),
new TemplateConfiguration.Builder().booleanFormat("J,N").build()
)
)
.customNumberFormats(ImmutableMap.of("baseN", BaseNTemplateNumberFormatFactory.INSTANCE))
.customDateFormats(ImmutableMap.of("epochDiv", EpochMillisDivTemplateDateFormatFactory.INSTANCE))
.autoImports(ImmutableMap.of("lib2", "lib2.f3ah"))
.autoIncludes(ImmutableList.of("inc2.f3ah"))
.sharedVariables(ImmutableMap.of("v2", 2))
.registeredCustomOutputFormats(
SeldomEscapedOutputFormat.INSTANCE, NameClashingDummyOutputFormat.INSTANCE)
.build();
TemplateConfigurationFactory tcf = cfg.getTemplateConfigurations();
assertEquals("Y,N", tcf.get("t.yn", null).getBooleanFormat());
assertEquals("J,N", tcf.get("t.jn", null).getBooleanFormat());
assertNotNull(cfg.getCustomNumberFormat("hex"));
assertNotNull(cfg.getCustomNumberFormat("baseN"));
assertNotNull(cfg.getCustomDateFormat("epoch"));
assertNotNull(cfg.getCustomDateFormat("epochDiv"));
assertEquals(ImmutableMap.of("lib", "lib.f3ah", "lib2", "lib2.f3ah"), cfg.getAutoImports());
assertEquals(ImmutableList.of("inc.f3ah", "inc2.f3ah"), cfg.getAutoIncludes());
assertEquals(ImmutableMap.of("v", 1, "v2", 2), cfg.getSharedVariables());
assertEquals(
ImmutableList.of(
CustomHTMLOutputFormat.INSTANCE,
SeldomEscapedOutputFormat.INSTANCE,
NameClashingDummyOutputFormat.INSTANCE),
cfg.getRegisteredCustomOutputFormats());
assertSame(CustomHTMLOutputFormat.INSTANCE, cfg.getOutputFormat("HTML"));
assertSame(NameClashingDummyOutputFormat.INSTANCE, cfg.getOutputFormat("dummy"));
assertSame(SeldomEscapedOutputFormat.INSTANCE, cfg.getOutputFormat("seldomEscaped"));
assertSame(XMLOutputFormat.INSTANCE, cfg.getOutputFormat("XML"));
}
@SuppressWarnings("boxing")
private void assertStartsWith(List<String> list, List<String> headList) {
int index = 0;
for (String name : headList) {
assertThat(index, lessThan(list.size()));
assertEquals(name, list.get(index));
index++;
}
}
private static class MyStringModel implements TemplateStringModel {
@Override
public String getAsString() throws TemplateException {
return "my";
}
}
private static class ImpliedSettingValuesTestBuilder
extends Configuration.ExtendableBuilder<ImpliedSettingValuesTestBuilder> {
ImpliedSettingValuesTestBuilder() {
super(VERSION_3_0_0);
}
@Override
protected TemplateConfigurationFactory getImpliedTemplateConfigurations() {
return new ConditionalTemplateConfigurationFactory(
new FileNameGlobMatcher("*.yn"),
new TemplateConfiguration.Builder().booleanFormat("Y,N").build());
}
@Override
protected Map<String, TemplateNumberFormatFactory> getImpliedCustomNumberFormats() {
return ImmutableMap.of(
"hex", HexTemplateNumberFormatFactory.INSTANCE);
}
@Override
protected Map<String, TemplateDateFormatFactory> getImpliedCustomDateFormats() {
return ImmutableMap.of(
"epoch", EpochMillisTemplateDateFormatFactory.INSTANCE);
}
@Override
protected Map<String, String> getImpliedAutoImports() {
return ImmutableMap.of("lib", "lib.f3ah");
}
@Override
protected List<String> getImpliedAutoIncludes() {
return ImmutableList.of("inc.f3ah");
}
@Override
protected Map<String, Object> getImpliedSharedVariables() {
return ImmutableMap.of("v", 1);
}
@Override
protected Collection<OutputFormat> getImpliedRegisteredCustomOutputFormats() {
return ImmutableList.of(CustomHTMLOutputFormat.INSTANCE, DummyOutputFormat.INSTANCE);
}
}
}