http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java new file mode 100644 index 0000000..dcefa3f --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConfigurationTest.java @@ -0,0 +1,1486 @@ +/* + * 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 static org.apache.freemarker.test.hamcerst.Matchers.*; +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.io.Serializable; +import java.io.StringWriter; +import java.lang.reflect.Field; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Set; +import java.util.TimeZone; + +import org.apache.freemarker.core.model.TemplateModelException; +import org.apache.freemarker.core.model.TemplateScalarModel; +import org.apache.freemarker.core.model.impl.DefaultObjectWrapper; +import org.apache.freemarker.core.model.impl.RestrictedObjectWrapper; +import org.apache.freemarker.core.model.impl.SimpleScalar; +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.UndefinedOutputFormat; +import org.apache.freemarker.core.outputformat.impl.XMLOutputFormat; +import org.apache.freemarker.core.templateresolver.CacheStorageWithGetSize; +import org.apache.freemarker.core.templateresolver.ConditionalTemplateConfigurationFactory; +import org.apache.freemarker.core.templateresolver.FileNameGlobMatcher; +import org.apache.freemarker.core.templateresolver.TemplateLookupContext; +import org.apache.freemarker.core.templateresolver.TemplateLookupResult; +import org.apache.freemarker.core.templateresolver.TemplateLookupStrategy; +import org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader; +import org.apache.freemarker.core.templateresolver.impl.ClassTemplateLoader; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateLookupStrategy; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormat; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateNameFormatFM2; +import org.apache.freemarker.core.templateresolver.impl.DefaultTemplateResolver; +import org.apache.freemarker.core.templateresolver.impl.NullCacheStorage; +import org.apache.freemarker.core.templateresolver.impl.SoftCacheStorage; +import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader; +import org.apache.freemarker.core.templateresolver.impl.StrongCacheStorage; +import org.apache.freemarker.core.userpkg.BaseNTemplateNumberFormatFactory; +import org.apache.freemarker.core.userpkg.CustomHTMLOutputFormat; +import org.apache.freemarker.core.userpkg.DummyOutputFormat; +import org.apache.freemarker.core.userpkg.EpochMillisDivTemplateDateFormatFactory; +import org.apache.freemarker.core.userpkg.EpochMillisTemplateDateFormatFactory; +import org.apache.freemarker.core.userpkg.HexTemplateNumberFormatFactory; +import org.apache.freemarker.core.util._DateUtil; +import org.apache.freemarker.core.util._NullArgumentException; +import org.apache.freemarker.core.util._NullWriter; +import org.apache.freemarker.core.util._StringUtil; +import org.apache.freemarker.core.valueformat.TemplateDateFormatFactory; +import org.apache.freemarker.core.valueformat.TemplateNumberFormatFactory; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; + +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import junit.framework.TestCase; + +public class ConfigurationTest extends TestCase { + + private static final Charset ISO_8859_2 = Charset.forName("ISO-8859-2"); + + public ConfigurationTest(String name) { + super(name); + } + + public void testUnsetAndIsSet() throws Exception { + Configuration.ExtendableBuilder<?> cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertFalse(cfgB.isLogTemplateExceptionsSet()); + assertFalse(cfgB.getLogTemplateExceptions()); + // + cfgB.setLogTemplateExceptions(true); + { + Configuration cfg = cfgB.build(); + assertTrue(cfgB.isLogTemplateExceptionsSet()); + assertTrue(cfg.isLogTemplateExceptionsSet()); + assertTrue(cfgB.getLogTemplateExceptions()); + assertTrue(cfg.getLogTemplateExceptions()); + } + // + for (int i = 0; i < 2; i++) { + cfgB.unsetLogTemplateExceptions(); + Configuration cfg = cfgB.build(); + assertFalse(cfgB.isLogTemplateExceptionsSet()); + assertTrue(cfg.isLogTemplateExceptionsSet()); + assertFalse(cfgB.getLogTemplateExceptions()); + assertFalse(cfg.getLogTemplateExceptions()); + } + + DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + assertFalse(cfgB.isObjectWrapperSet()); + assertSame(dow, cfgB.getObjectWrapper()); + // + RestrictedObjectWrapper ow = new RestrictedObjectWrapper.Builder(Configuration.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.DEBUG_HANDLER, cfgB.getTemplateExceptionHandler()); + // + cfgB.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); + assertTrue(cfgB.isTemplateExceptionHandlerSet()); + assertSame(TemplateExceptionHandler.RETHROW_HANDLER, cfgB.getTemplateExceptionHandler()); + // + for (int i = 0; i < 2; i++) { + cfgB.unsetTemplateExceptionHandler(); + assertFalse(cfgB.isTemplateExceptionHandlerSet()); + assertSame(TemplateExceptionHandler.DEBUG_HANDLER, 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(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat()); + // + cfgB.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE); + assertTrue(cfgB.isTemplateNameFormatSet()); + assertSame(DefaultTemplateNameFormat.INSTANCE, cfgB.getTemplateNameFormat()); + // + for (int i = 0; i < 2; i++) { + cfgB.unsetTemplateNameFormat(); + assertFalse(cfgB.isTemplateNameFormatSet()); + assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat()); + } + + assertFalse(cfgB.isCacheStorageSet()); + assertTrue(cfgB.getCacheStorage() instanceof SoftCacheStorage); + // + cfgB.setCacheStorage(NullCacheStorage.INSTANCE); + assertTrue(cfgB.isCacheStorageSet()); + assertSame(NullCacheStorage.INSTANCE, cfgB.getCacheStorage()); + // + for (int i = 0; i < 3; i++) { + if (i == 2) { + cfgB.setCacheStorage(cfgB.getCacheStorage()); + } + cfgB.unsetCacheStorage(); + assertFalse(cfgB.isCacheStorageSet()); + assertTrue(cfgB.getCacheStorage() instanceof SoftCacheStorage); + } + } + + public void testTemplateLoadingErrors() throws Exception { + Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0) + .templateLoader(new ClassTemplateLoader(getClass(), "nosuchpackage")) + .build(); + try { + cfg.getTemplate("missing.ftl"); + fail(); + } catch (TemplateNotFoundException e) { + assertThat(e.getMessage(), not(containsString("wasn't set"))); + } + } + + public void testVersion() { + Version v = Configuration.getVersion(); + assertTrue(v.intValue() >= _CoreAPI.VERSION_INT_3_0_0); + + try { + new Configuration.Builder(new Version(999, 1, 2)); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("upgrade")); + } + + try { + new Configuration.Builder(new Version(2, 3, 0)); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("3.0.0")); + } + } + + public void testShowErrorTips() throws Exception { + try { + Configuration cfg = new Configuration.Builder(Configuration.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 Configuration.Builder(Configuration.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.ftl"; + final String tHuFtl = "t_hu.ftl"; + final String tEnFtl = "t_en.ftl"; + final String tUtf8Ftl = "utf8.ftl"; + 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 Configuration.Builder(Configuration.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, (Locale) 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.ftl", hu, custLookupCond, false); + fail(); + } catch (TemplateNotFoundException e) { + // Expected + } + assertNull(cfg.getTemplate("missing.ftl", 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()); + } + + public void testTemplateResolverCache() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + CacheStorageWithGetSize cache = (CacheStorageWithGetSize) cfgB.getCacheStorage(); + assertEquals(0, cache.getSize()); + cfgB.setCacheStorage(new StrongCacheStorage()); + cache = (CacheStorageWithGetSize) cfgB.getCacheStorage(); + assertEquals(0, cache.getSize()); + cfgB.setTemplateLoader(new ClassTemplateLoader(ConfigurationTest.class, "")); + Configuration cfg = cfgB.build(); + assertEquals(0, cache.getSize()); + cfg.getTemplate("toCache1.ftl"); + assertEquals(1, cache.getSize()); + cfg.getTemplate("toCache2.ftl"); + assertEquals(2, cache.getSize()); + cfg.clearTemplateCache(); + assertEquals(0, cache.getSize()); + cfg.getTemplate("toCache1.ftl"); + assertEquals(1, cache.getSize()); + cfgB.setTemplateLoader(cfgB.getTemplateLoader()); + assertEquals(1, cache.getSize()); + } + + public void testTemplateNameFormat() throws Exception { + StringTemplateLoader tl = new StringTemplateLoader(); + tl.putTemplate("a/b.ftl", "In a/b.ftl"); + tl.putTemplate("b.ftl", "In b.ftl"); + + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0) + .templateLoader(tl); + + { + cfgB.setTemplateNameFormat(DefaultTemplateNameFormatFM2.INSTANCE); + final Template template = cfgB.build().getTemplate("a/./../b.ftl"); + assertEquals("a/b.ftl", template.getLookupName()); + assertEquals("a/b.ftl", template.getSourceName()); + assertEquals("In a/b.ftl", template.toString()); + } + + { + cfgB.setTemplateNameFormat(DefaultTemplateNameFormat.INSTANCE); + final Template template = cfgB.build().getTemplate("a/./../b.ftl"); + assertEquals("b.ftl", template.getLookupName()); + assertEquals("b.ftl", template.getSourceName()); + assertEquals("In b.ftl", template.toString()); + } + } + + public void testTemplateNameFormatSetSetting() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY, "defAult_2_4_0"); + assertSame(DefaultTemplateNameFormat.INSTANCE, cfgB.getTemplateNameFormat()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY, "defaUlt_2_3_0"); + assertSame(DefaultTemplateNameFormatFM2.INSTANCE, cfgB.getTemplateNameFormat()); + assertTrue(cfgB.isTemplateNameFormatSet()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_NAME_FORMAT_KEY, "defauLt"); + assertFalse(cfgB.isTemplateNameFormatSet()); + } + + public void testObjectWrapperSetSetting() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + { + cfgB.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "defAult"); + DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build(); + assertSame(dow, cfgB.getObjectWrapper()); + assertEquals(Configuration.VERSION_3_0_0, dow.getIncompatibleImprovements()); + } + + { + cfgB.setSetting(MutableProcessingConfiguration.OBJECT_WRAPPER_KEY, "restricted"); + assertThat(cfgB.getObjectWrapper(), instanceOf(RestrictedObjectWrapper.class)); + } + } + + public void testTemplateLookupStrategyDefaultAndSet() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.getTemplateLookupStrategy()); + assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.build().getTemplateLookupStrategy()); + + cfgB.setTemplateLoader(new ClassTemplateLoader(ConfigurationTest.class, "")); + assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfgB.getTemplateLookupStrategy()); + Configuration cfg = cfgB.build(); + assertSame(DefaultTemplateLookupStrategy.INSTANCE, cfg.getTemplateLookupStrategy()); + cfg.getTemplate("toCache1.ftl"); + + final TemplateLookupStrategy myStrategy = new TemplateLookupStrategy() { + @Override + public TemplateLookupResult lookup(TemplateLookupContext ctx) throws IOException { + return ctx.lookupWithAcquisitionStrategy("toCache2.ftl"); + } + }; + cfgB.setTemplateLookupStrategy(myStrategy); + assertSame(myStrategy, cfgB.getTemplateLookupStrategy()); + cfg = cfgB.build(); + cfg.clearTemplateCache(); + assertSame(myStrategy, cfg.getTemplateLookupStrategy()); + Template template = cfg.getTemplate("toCache1.ftl"); + assertEquals("toCache2.ftl", template.getSourceName()); + } + + public void testSetTemplateConfigurations() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertNull(cfgB.getTemplateConfigurations()); + + StringTemplateLoader tl = new StringTemplateLoader(); + tl.putTemplate("t.de.ftlh", ""); + tl.putTemplate("t.fr.ftlx", ""); + tl.putTemplate("t.ftlx", ""); + tl.putTemplate("Stat/t.de.ftlx", ""); + cfgB.setTemplateLoader(tl); + + cfgB.setTimeZone(TimeZone.getTimeZone("GMT+09")); + + cfgB.setSetting(Configuration.ExtendableBuilder.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('ftlh'), TemplateConfiguration(booleanFormat='TODO,HTML')), " + + "ConditionalTemplateConfigurationFactory(" + + "FileExtensionMatcher('ftlx'), 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.ftlh"); + assertEquals("TODO,HTML", t.getBooleanFormat()); + assertEquals(TimeZone.getTimeZone("GMT+01"), t.getTimeZone()); + } + { + Template t = cfg.getTemplate("t.fr.ftlx"); + assertEquals("TODO,XML", t.getBooleanFormat()); + assertEquals(TimeZone.getTimeZone("GMT"), t.getTimeZone()); + } + { + Template t = cfg.getTemplate("t.ftlx"); + assertEquals("TODO,XML", t.getBooleanFormat()); + assertEquals(TimeZone.getTimeZone("GMT+09"), t.getTimeZone()); + } + { + Template t = cfg.getTemplate("Stat/t.de.ftlx"); + assertEquals("TODO,XML", t.getBooleanFormat()); + assertEquals(_DateUtil.UTC, t.getTimeZone()); + } + + assertNotNull(cfgB.getTemplateConfigurations()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_CONFIGURATIONS_KEY, "null"); + assertNull(cfgB.getTemplateConfigurations()); + } + + public void testSetAutoEscaping() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setAutoEscapingPolicy(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY); + assertEquals(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setAutoEscapingPolicy(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY); + assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setAutoEscapingPolicy(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY); + assertEquals(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enableIfSupported"); + assertEquals(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enable_if_supported"); + assertEquals(ParsingConfiguration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enableIfDefault"); + assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "enable_if_default"); + assertEquals(ParsingConfiguration.ENABLE_IF_DEFAULT_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + cfgB.setSetting(Configuration.ExtendableBuilder.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE, "disable"); + assertEquals(ParsingConfiguration.DISABLE_AUTO_ESCAPING_POLICY, cfgB.getAutoEscapingPolicy()); + + try { + cfgB.setAutoEscapingPolicy(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + } + + public void testSetOutputFormat() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertEquals(UndefinedOutputFormat.INSTANCE, cfgB.getOutputFormat()); + assertFalse(cfgB.isOutputFormatSet()); + + try { + cfgB.setOutputFormat(null); + fail(); + } catch (_NullArgumentException e) { + // Expected + } + + assertFalse(cfgB.isOutputFormatSet()); + + cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE, XMLOutputFormat.class.getSimpleName()); + assertEquals(XMLOutputFormat.INSTANCE, cfgB.getOutputFormat()); + + cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_SNAKE_CASE, HTMLOutputFormat.class.getSimpleName()); + assertEquals(HTMLOutputFormat.INSTANCE, cfgB.getOutputFormat()); + + cfgB.unsetOutputFormat(); + assertEquals(UndefinedOutputFormat.INSTANCE, cfgB.getOutputFormat()); + assertFalse(cfgB.isOutputFormatSet()); + + cfgB.setOutputFormat(UndefinedOutputFormat.INSTANCE); + assertTrue(cfgB.isOutputFormatSet()); + cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY_CAMEL_CASE, "default"); + assertFalse(cfgB.isOutputFormatSet()); + + try { + cfgB.setSetting(Configuration.ExtendableBuilder.OUTPUT_FORMAT_KEY, "null"); + } catch (ConfigurationSettingValueException e) { + assertThat(e.getCause().getMessage(), containsString(UndefinedOutputFormat.class.getSimpleName())); + } + } + + @Test + public void testGetOutputFormatByName() throws Exception { + Configuration cfg = new Configuration.Builder(Configuration.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"))); + } + } + + public void testSetRegisteredCustomOutputFormats() throws Exception { + Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertTrue(cfg.getRegisteredCustomOutputFormats().isEmpty()); + + cfg.setSetting(Configuration.ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_CAMEL_CASE, + "[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(Configuration.ExtendableBuilder.REGISTERED_CUSTOM_OUTPUT_FORMATS_KEY_SNAKE_CASE, "[TemplateConfiguration()]"); + fail(); + } catch (ConfigurationSettingValueException e) { + assertThat(e.getMessage(), containsString(OutputFormat.class.getSimpleName())); + } + } + + public void testSetRecognizeStandardFileExtensions() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertTrue(cfgB.getRecognizeStandardFileExtensions()); + assertFalse(cfgB.isRecognizeStandardFileExtensionsSet()); + + cfgB.setRecognizeStandardFileExtensions(false); + assertFalse(cfgB.getRecognizeStandardFileExtensions()); + assertTrue(cfgB.isRecognizeStandardFileExtensionsSet()); + + cfgB.unsetRecognizeStandardFileExtensions(); + assertTrue(cfgB.getRecognizeStandardFileExtensions()); + assertFalse(cfgB.isRecognizeStandardFileExtensionsSet()); + + cfgB.setRecognizeStandardFileExtensions(true); + assertTrue(cfgB.getRecognizeStandardFileExtensions()); + assertTrue(cfgB.isRecognizeStandardFileExtensionsSet()); + + cfgB.setSetting(Configuration.ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE, "false"); + assertFalse(cfgB.getRecognizeStandardFileExtensions()); + assertTrue(cfgB.isRecognizeStandardFileExtensionsSet()); + + cfgB.setSetting(Configuration.ExtendableBuilder.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_SNAKE_CASE, "default"); + assertTrue(cfgB.getRecognizeStandardFileExtensions()); + assertFalse(cfgB.isRecognizeStandardFileExtensionsSet()); + } + + public void testSetTimeZone() throws ConfigurationException { + TimeZone origSysDefTZ = TimeZone.getDefault(); + try { + TimeZone sysDefTZ = TimeZone.getTimeZone("GMT-01"); + TimeZone.setDefault(sysDefTZ); + + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertEquals(sysDefTZ, cfgB.getTimeZone()); + cfgB.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default"); + assertEquals(sysDefTZ, cfgB.getTimeZone()); + + TimeZone newSysDefTZ = TimeZone.getTimeZone("GMT+09"); + TimeZone.setDefault(newSysDefTZ); + assertEquals(sysDefTZ, cfgB.getTimeZone()); + cfgB.setSetting(MutableProcessingConfiguration.TIME_ZONE_KEY, "JVM default"); + assertEquals(newSysDefTZ, cfgB.getTimeZone()); + } finally { + TimeZone.setDefault(origSysDefTZ); + } + } + + public void testSetSQLDateAndTimeTimeZone() throws ConfigurationException { + TimeZone origSysDefTZ = TimeZone.getDefault(); + try { + TimeZone sysDefTZ = TimeZone.getTimeZone("GMT-01"); + TimeZone.setDefault(sysDefTZ); + + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertNull(cfgB.getSQLDateAndTimeTimeZone()); + + cfgB.setSQLDateAndTimeTimeZone(null); + assertNull(cfgB.getSQLDateAndTimeTimeZone()); + + cfgB.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "JVM default"); + assertEquals(sysDefTZ, cfgB.getSQLDateAndTimeTimeZone()); + + cfgB.setSetting(MutableProcessingConfiguration.SQL_DATE_AND_TIME_TIME_ZONE_KEY, "null"); + assertNull(cfgB.getSQLDateAndTimeTimeZone()); + } finally { + TimeZone.setDefault(origSysDefTZ); + } + } + + public void testTimeZoneLayers() throws Exception { + TimeZone localTZ = TimeZone.getTimeZone("Europe/Brussels"); + + { + Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build(); + Template t = new Template(null, "", cfg); + Environment env1 = t.createProcessingEnvironment(null, new StringWriter()); + Environment env2 = t.createProcessingEnvironment(null, new StringWriter()); + + // cfg: + assertEquals(TimeZone.getDefault(), cfg.getTimeZone()); + assertNull(cfg.getSQLDateAndTimeTimeZone()); + // env: + assertEquals(TimeZone.getDefault(), env1.getTimeZone()); + assertNull(env1.getSQLDateAndTimeTimeZone()); + // env 2: + assertEquals(TimeZone.getDefault(), env2.getTimeZone()); + assertNull(env2.getSQLDateAndTimeTimeZone()); + + env1.setSQLDateAndTimeTimeZone(_DateUtil.UTC); + // cfg: + assertEquals(TimeZone.getDefault(), cfg.getTimeZone()); + assertNull(cfg.getSQLDateAndTimeTimeZone()); + // env: + assertEquals(TimeZone.getDefault(), env1.getTimeZone()); + assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone()); + + env1.setTimeZone(localTZ); + // cfg: + assertEquals(TimeZone.getDefault(), cfg.getTimeZone()); + assertNull(cfg.getSQLDateAndTimeTimeZone()); + // env: + assertEquals(localTZ, env1.getTimeZone()); + assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone()); + // env 2: + assertEquals(TimeZone.getDefault(), env2.getTimeZone()); + assertNull(env2.getSQLDateAndTimeTimeZone()); + } + + { + TimeZone otherTZ1 = TimeZone.getTimeZone("GMT+05"); + TimeZone otherTZ2 = TimeZone.getTimeZone("GMT+06"); + Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0) + .timeZone(otherTZ1) + .sqlDateAndTimeTimeZone(otherTZ2) + .build(); + + Template t = new Template(null, "", cfg); + Environment env1 = t.createProcessingEnvironment(null, new StringWriter()); + Environment env2 = t.createProcessingEnvironment(null, new StringWriter()); + + env1.setTimeZone(localTZ); + env1.setSQLDateAndTimeTimeZone(_DateUtil.UTC); + + // cfg: + assertEquals(otherTZ1, cfg.getTimeZone()); + assertEquals(otherTZ2, cfg.getSQLDateAndTimeTimeZone()); + // env: + assertEquals(localTZ, env1.getTimeZone()); + assertEquals(_DateUtil.UTC, env1.getSQLDateAndTimeTimeZone()); + // env 2: + assertEquals(otherTZ1, env2.getTimeZone()); + assertEquals(otherTZ2, env2.getSQLDateAndTimeTimeZone()); + + try { + setTimeZoneToNull(env2); + fail(); + } catch (IllegalArgumentException e) { + // expected + } + env2.setSQLDateAndTimeTimeZone(null); + assertEquals(otherTZ1, env2.getTimeZone()); + assertNull(env2.getSQLDateAndTimeTimeZone()); + } + } + + @SuppressFBWarnings(value="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS", justification="Expected to fail") + private void setTimeZoneToNull(Environment env2) { + env2.setTimeZone(null); + } + + public void testSetICIViaSetSettingAPI() throws ConfigurationException { + Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertEquals(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS, cfg.getIncompatibleImprovements()); + // This is the only valid value ATM: + cfg.setSetting(Configuration.ExtendableBuilder.INCOMPATIBLE_IMPROVEMENTS_KEY, "3.0.0"); + assertEquals(Configuration.VERSION_3_0_0, cfg.getIncompatibleImprovements()); + } + + public void testSetLogTemplateExceptionsViaSetSettingAPI() throws ConfigurationException { + Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertFalse(cfg.getLogTemplateExceptions()); + cfg.setSetting(MutableProcessingConfiguration.LOG_TEMPLATE_EXCEPTIONS_KEY, "true"); + assertTrue(cfg.getLogTemplateExceptions()); + } + + public void testSharedVariables() throws TemplateException, IOException { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + Map<String, Object> vars = new HashMap<>(); + vars.put("a", "aa"); + vars.put("b", "bb"); + vars.put("c", new MyScalarModel()); + cfgB.setSharedVariables(vars); + + assertNull(cfgB.getSharedVariable("erased")); + + { + Configuration cfg = cfgB.build(); + + TemplateScalarModel aVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("a"); + assertEquals("aa", aVal.getAsString()); + assertEquals(SimpleScalar.class, aVal.getClass()); + + TemplateScalarModel bVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("b"); + assertEquals("bb", bVal.getAsString()); + assertEquals(SimpleScalar.class, bVal.getClass()); + + TemplateScalarModel cVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("c"); + assertEquals("my", cVal.getAsString()); + assertEquals(MyScalarModel.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()); + } + + cfgB.setSharedVariable("b", "bbLegacy"); + + { + Configuration cfg = cfgB.build(); + + TemplateScalarModel aVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("a"); + assertEquals("aa", aVal.getAsString()); + assertEquals(SimpleScalar.class, aVal.getClass()); + + TemplateScalarModel bVal = (TemplateScalarModel) cfg.getWrappedSharedVariable("b"); + assertEquals("bbLegacy", bVal.getAsString()); + assertEquals(SimpleScalar.class, bVal.getClass()); + } + } + + @Test + public void testApiBuiltinEnabled() throws Exception { + try { + new Template( + null, "${1?api}", + new Configuration.Builder(Configuration.VERSION_3_0_0).build()) + .process(null, _NullWriter.INSTANCE); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), containsString(MutableProcessingConfiguration.API_BUILTIN_ENABLED_KEY)); + } + + new Template( + null, "${m?api.hashCode()}", + new Configuration.Builder(Configuration.VERSION_3_0_0).apiBuiltinEnabled(true).build()) + .process(Collections.singletonMap("m", new HashMap()), _NullWriter.INSTANCE); + } + + @Test + public void testTemplateUpdateDelay() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertEquals(DefaultTemplateResolver.DEFAULT_TEMPLATE_UPDATE_DELAY_MILLIS, cfgB.getTemplateUpdateDelayMilliseconds()); + + cfgB.setTemplateUpdateDelayMilliseconds(4000); + assertEquals(4000L, cfgB.getTemplateUpdateDelayMilliseconds()); + + cfgB.setTemplateUpdateDelayMilliseconds(100); + assertEquals(100L, cfgB.getTemplateUpdateDelayMilliseconds()); + + try { + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "5"); + assertEquals(5000L, cfgB.getTemplateUpdateDelayMilliseconds()); + } catch (ConfigurationSettingValueException e) { + assertThat(e.getMessage(), containsStringIgnoringCase("unit must be specified")); + } + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "0"); + assertEquals(0L, cfgB.getTemplateUpdateDelayMilliseconds()); + try { + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "5 foo"); + assertEquals(5000L, cfgB.getTemplateUpdateDelayMilliseconds()); + } catch (ConfigurationSettingValueException e) { + assertThat(e.getMessage(), containsStringIgnoringCase("\"foo\"")); + } + + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "3 ms"); + assertEquals(3L, cfgB.getTemplateUpdateDelayMilliseconds()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "4ms"); + assertEquals(4L, cfgB.getTemplateUpdateDelayMilliseconds()); + + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "3 s"); + assertEquals(3000L, cfgB.getTemplateUpdateDelayMilliseconds()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "4s"); + assertEquals(4000L, cfgB.getTemplateUpdateDelayMilliseconds()); + + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "3 m"); + assertEquals(1000L * 60 * 3, cfgB.getTemplateUpdateDelayMilliseconds()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "4m"); + assertEquals(1000L * 60 * 4, cfgB.getTemplateUpdateDelayMilliseconds()); + + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "1 h"); + assertEquals(1000L * 60 * 60, cfgB.getTemplateUpdateDelayMilliseconds()); + cfgB.setSetting(Configuration.ExtendableBuilder.TEMPLATE_UPDATE_DELAY_KEY, "2h"); + assertEquals(1000L * 60 * 60 * 2, cfgB.getTemplateUpdateDelayMilliseconds()); + } + + @Test + @SuppressFBWarnings(value = "NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS ", justification = "Testing wrong args") + public void testSetCustomNumberFormat() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + try { + cfgB.setCustomNumberFormats(null); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("null")); + } + + try { + cfgB.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>singletonMap( + "", HexTemplateNumberFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("0 length")); + } + + try { + cfgB.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>singletonMap( + "a_b", HexTemplateNumberFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("a_b")); + } + + try { + cfgB.setCustomNumberFormats(Collections.<String, TemplateNumberFormatFactory>singletonMap( + "a b", HexTemplateNumberFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("a b")); + } + + try { + cfgB.setCustomNumberFormats(ImmutableMap.<String, TemplateNumberFormatFactory>of( + "a", HexTemplateNumberFormatFactory.INSTANCE, + "@wrong", HexTemplateNumberFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("@wrong")); + } + + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE, + "{ 'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "() }"); + assertEquals( + Collections.singletonMap("base", BaseNTemplateNumberFormatFactory.INSTANCE), + cfgB.getCustomNumberFormats()); + + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_SNAKE_CASE, + "{ " + + "'base': " + BaseNTemplateNumberFormatFactory.class.getName() + "(), " + + "'hex': " + HexTemplateNumberFormatFactory.class.getName() + "()" + + " }"); + assertEquals( + ImmutableMap.of( + "base", BaseNTemplateNumberFormatFactory.INSTANCE, + "hex", HexTemplateNumberFormatFactory.INSTANCE), + cfgB.getCustomNumberFormats()); + + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY, "{}"); + assertEquals(Collections.emptyMap(), cfgB.getCustomNumberFormats()); + + try { + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_NUMBER_FORMATS_KEY_CAMEL_CASE, + "{ 'x': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }"); + fail(); + } catch (ConfigurationException e) { + assertThat(e.getCause().getMessage(), allOf( + containsString(EpochMillisTemplateDateFormatFactory.class.getName()), + containsString(TemplateNumberFormatFactory.class.getName()))); + } + } + + @Test + public void testSetTabSize() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + String ftl = "${\t}"; + + try { + new Template(null, ftl, cfgB.build()); + fail(); + } catch (ParseException e) { + assertEquals(9, e.getColumnNumber()); + } + + cfgB.setTabSize(1); + try { + new Template(null, ftl, cfgB.build()); + fail(); + } catch (ParseException e) { + assertEquals(4, e.getColumnNumber()); + } + + try { + cfgB.setTabSize(0); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + + try { + cfgB.setTabSize(257); + fail(); + } catch (IllegalArgumentException e) { + // Expected + } + } + + @Test + public void testTabSizeSetting() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + assertEquals(8, cfgB.getTabSize()); + cfgB.setSetting(Configuration.ExtendableBuilder.TAB_SIZE_KEY_CAMEL_CASE, "4"); + assertEquals(4, cfgB.getTabSize()); + cfgB.setSetting(Configuration.ExtendableBuilder.TAB_SIZE_KEY_SNAKE_CASE, "1"); + assertEquals(1, cfgB.getTabSize()); + + try { + cfgB.setSetting(Configuration.ExtendableBuilder.TAB_SIZE_KEY_SNAKE_CASE, "x"); + fail(); + } catch (ConfigurationException e) { + assertThat(e.getCause(), instanceOf(NumberFormatException.class)); + } + } + + @SuppressFBWarnings(value="NP_NULL_PARAM_DEREF_ALL_TARGETS_DANGEROUS", justification="We test failures") + @Test + public void testSetCustomDateFormat() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + try { + cfgB.setCustomDateFormats(null); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("null")); + } + + try { + cfgB.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>singletonMap( + "", EpochMillisTemplateDateFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("0 length")); + } + + try { + cfgB.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>singletonMap( + "a_b", EpochMillisTemplateDateFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("a_b")); + } + + try { + cfgB.setCustomDateFormats(Collections.<String, TemplateDateFormatFactory>singletonMap( + "a b", EpochMillisTemplateDateFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("a b")); + } + + try { + cfgB.setCustomDateFormats(ImmutableMap.<String, TemplateDateFormatFactory>of( + "a", EpochMillisTemplateDateFormatFactory.INSTANCE, + "@wrong", EpochMillisTemplateDateFormatFactory.INSTANCE)); + fail(); + } catch (IllegalArgumentException e) { + assertThat(e.getMessage(), containsString("@wrong")); + } + + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE, + "{ 'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "() }"); + assertEquals( + Collections.singletonMap("epoch", EpochMillisTemplateDateFormatFactory.INSTANCE), + cfgB.getCustomDateFormats()); + + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_SNAKE_CASE, + "{ " + + "'epoch': " + EpochMillisTemplateDateFormatFactory.class.getName() + "(), " + + "'epochDiv': " + EpochMillisDivTemplateDateFormatFactory.class.getName() + "()" + + " }"); + assertEquals( + ImmutableMap.of( + "epoch", EpochMillisTemplateDateFormatFactory.INSTANCE, + "epochDiv", EpochMillisDivTemplateDateFormatFactory.INSTANCE), + cfgB.getCustomDateFormats()); + + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY, "{}"); + assertEquals(Collections.emptyMap(), cfgB.getCustomDateFormats()); + + try { + cfgB.setSetting(MutableProcessingConfiguration.CUSTOM_DATE_FORMATS_KEY_CAMEL_CASE, + "{ 'x': " + HexTemplateNumberFormatFactory.class.getName() + "() }"); + fail(); + } catch (ConfigurationException e) { + assertThat(e.getCause().getMessage(), allOf( + containsString(HexTemplateNumberFormatFactory.class.getName()), + containsString(TemplateDateFormatFactory.class.getName()))); + } + } + + public void testNamingConventionSetSetting() throws ConfigurationException { + Configuration.Builder cfg = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention()); + + cfg.setSetting("naming_convention", "legacy"); + assertEquals(ParsingConfiguration.LEGACY_NAMING_CONVENTION, cfg.getNamingConvention()); + + cfg.setSetting("naming_convention", "camel_case"); + assertEquals(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION, cfg.getNamingConvention()); + + cfg.setSetting("naming_convention", "auto_detect"); + assertEquals(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION, cfg.getNamingConvention()); + } + + public void testLazyImportsSetSetting() throws ConfigurationException { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertFalse(cfgB.getLazyImports()); + assertFalse(cfgB.isLazyImportsSet()); + cfgB.setSetting("lazy_imports", "true"); + assertTrue(cfgB.getLazyImports()); + cfgB.setSetting("lazyImports", "false"); + assertFalse(cfgB.getLazyImports()); + assertTrue(cfgB.isLazyImportsSet()); + } + + public void testLazyAutoImportsSetSetting() throws ConfigurationException { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertNull(cfgB.getLazyAutoImports()); + assertFalse(cfgB.isLazyAutoImportsSet()); + cfgB.setSetting("lazy_auto_imports", "true"); + assertEquals(Boolean.TRUE, cfgB.getLazyAutoImports()); + assertTrue(cfgB.isLazyAutoImportsSet()); + cfgB.setSetting("lazyAutoImports", "false"); + assertEquals(Boolean.FALSE, cfgB.getLazyAutoImports()); + cfgB.setSetting("lazyAutoImports", "null"); + assertNull(cfgB.getLazyAutoImports()); + assertTrue(cfgB.isLazyAutoImportsSet()); + cfgB.unsetLazyAutoImports(); + assertNull(cfgB.getLazyAutoImports()); + assertFalse(cfgB.isLazyAutoImportsSet()); + } + + public void testLocaleSetting() throws TemplateException, ConfigurationException { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertEquals(Locale.getDefault(), cfgB.getLocale()); + assertFalse(cfgB.isLocaleSet()); + + Locale nonDefault = Locale.getDefault().equals(Locale.GERMANY) ? Locale.FRANCE : Locale.GERMANY; + cfgB.setLocale(nonDefault); + assertTrue(cfgB.isLocaleSet()); + assertEquals(nonDefault, cfgB.getLocale()); + + cfgB.unsetLocale(); + assertEquals(Locale.getDefault(), cfgB.getLocale()); + assertFalse(cfgB.isLocaleSet()); + + cfgB.setSetting(Configuration.ExtendableBuilder.LOCALE_KEY, "JVM default"); + assertEquals(Locale.getDefault(), cfgB.getLocale()); + assertTrue(cfgB.isLocaleSet()); + } + + public void testDefaultEncodingSetting() throws TemplateException, ConfigurationException { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertEquals(Charset.defaultCharset(), cfgB.getSourceEncoding()); + assertFalse(cfgB.isSourceEncodingSet()); + + Charset nonDefault = Charset.defaultCharset().equals(StandardCharsets.UTF_8) ? StandardCharsets.ISO_8859_1 + : StandardCharsets.UTF_8; + cfgB.setSourceEncoding(nonDefault); + assertTrue(cfgB.isSourceEncodingSet()); + assertEquals(nonDefault, cfgB.getSourceEncoding()); + + cfgB.unsetSourceEncoding(); + assertEquals(Charset.defaultCharset(), cfgB.getSourceEncoding()); + assertFalse(cfgB.isSourceEncodingSet()); + + cfgB.setSetting(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY, "JVM default"); + assertEquals(Charset.defaultCharset(), cfgB.getSourceEncoding()); + assertTrue(cfgB.isSourceEncodingSet()); + } + + public void testTimeZoneSetting() throws TemplateException, ConfigurationException { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + assertEquals(TimeZone.getDefault(), cfgB.getTimeZone()); + assertFalse(cfgB.isTimeZoneSet()); + + TimeZone nonDefault = TimeZone.getDefault().equals(_DateUtil.UTC) ? TimeZone.getTimeZone("PST") : _DateUtil.UTC; + cfgB.setTimeZone(nonDefault); + assertTrue(cfgB.isTimeZoneSet()); + assertEquals(nonDefault, cfgB.getTimeZone()); + + cfgB.unsetTimeZone(); + assertEquals(TimeZone.getDefault(), cfgB.getTimeZone()); + assertFalse(cfgB.isTimeZoneSet()); + + cfgB.setSetting(Configuration.ExtendableBuilder.TIME_ZONE_KEY, "JVM default"); + assertEquals(TimeZone.getDefault(), cfgB.getTimeZone()); + assertTrue(cfgB.isTimeZoneSet()); + } + + @Test + public void testGetSettingNamesAreSorted() throws Exception { + Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build(); + for (boolean camelCase : new boolean[] { false, true }) { + List<String> names = new ArrayList<>(Configuration.Builder.getSettingNames(camelCase)); + List<String> procCfgNames = new ArrayList<>(new Template(null, "", cfg) + .createProcessingEnvironment(null, _NullWriter.INSTANCE) + .getSettingNames(camelCase)); + assertStartsWith(names, procCfgNames); + + String prevName = null; + for (int i = procCfgNames.size(); i < names.size(); i++) { + String name = names.get(i); + if (prevName != null) { + assertThat(name, greaterThan(prevName)); + } + prevName = name; + } + } + } + + @Test + @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") + public void testGetSettingNamesNameConventionsContainTheSame() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + ConfigurableTest.testGetSettingNamesNameConventionsContainTheSame( + new ArrayList<>(cfgB.getSettingNames(false)), + new ArrayList<>(cfgB.getSettingNames(true))); + } + + @Test + @SuppressFBWarnings("DLS_DEAD_LOCAL_STORE") + public void testStaticFieldKeysCoverAllGetSettingNames() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + List<String> names = new ArrayList<>(cfgB.getSettingNames(false)); + List<String> cfgableNames = new ArrayList<>(cfgB.getSettingNames(false)); + assertStartsWith(names, cfgableNames); + + for (int i = cfgableNames.size(); i < names.size(); i++) { + String name = names.get(i); + assertTrue("No field was found for " + name, keyFieldExists(name)); + } + } + + @Test + public void testGetSettingNamesCoversAllStaticKeyFields() throws Exception { + Collection<String> names = new Configuration.Builder(Configuration.VERSION_3_0_0).getSettingNames(false); + + for (Class<? extends MutableProcessingConfiguration> cfgableClass : new Class[] { Configuration.class, MutableProcessingConfiguration.class }) { + for (Field f : cfgableClass.getFields()) { + if (f.getName().endsWith("_KEY")) { + final Object name = f.get(null); + assertTrue("Missing setting name: " + name, names.contains(name)); + } + } + } + } + + @Test + public void testKeyStaticFieldsHasAllVariationsAndCorrectFormat() throws IllegalArgumentException, IllegalAccessException { + ConfigurableTest.testKeyStaticFieldsHasAllVariationsAndCorrectFormat(Configuration.ExtendableBuilder.class); + } + + @Test + public void testGetSettingNamesCoversAllSettingNames() throws Exception { + Collection<String> names = new Configuration.Builder(Configuration.VERSION_3_0_0).getSettingNames(false); + + for (Field f : MutableProcessingConfiguration.class.getFields()) { + if (f.getName().endsWith("_KEY")) { + final Object name = f.get(null); + assertTrue("Missing setting name: " + name, names.contains(name)); + } + } + } + + @Test + public void testSetSettingSupportsBothNamingConventions() throws Exception { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.VERSION_3_0_0); + + cfgB.setSetting(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY_CAMEL_CASE, StandardCharsets.UTF_16LE.name()); + assertEquals(StandardCharsets.UTF_16LE, cfgB.getSourceEncoding()); + cfgB.setSetting(Configuration.ExtendableBuilder.SOURCE_ENCODING_KEY_SNAKE_CASE, StandardCharsets.UTF_8.name()); + assertEquals(StandardCharsets.UTF_8, cfgB.getSourceEncoding()); + + for (String nameCC : cfgB.getSettingNames(true)) { + for (String value : new String[] { "1", "default", "true" }) { + Exception resultCC = null; + try { + cfgB.setSetting(nameCC, value); + } catch (Exception e) { + assertThat(e, not(instanceOf(UnknownConfigurationSettingException.class))); + resultCC = e; + } + + String nameSC = _StringUtil.camelCaseToUnderscored(nameCC); + Exception resultSC = null; + try { + cfgB.setSetting(nameSC, value); + } catch (Exception e) { + assertThat(e, not(instanceOf(UnknownConfigurationSettingException.class))); + resultSC = e; + } + + if (resultCC == null) { + assertNull(resultSC); + } else { + assertNotNull(resultSC); + assertEquals(resultCC.getClass(), resultSC.getClass()); + } + } + } + } + + @Test + public void testGetSupportedBuiltInDirectiveNames() { + Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build(); + + Set<String> allNames = cfg.getSupportedBuiltInDirectiveNames(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION); + Set<String> lNames = cfg.getSupportedBuiltInDirectiveNames(ParsingConfiguration.LEGACY_NAMING_CONVENTION); + Set<String> cNames = cfg.getSupportedBuiltInDirectiveNames(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + + checkNamingConventionNameSets(allNames, lNames, cNames); + + for (String name : cNames) { + assertThat(name.toLowerCase(), isIn(lNames)); + } + } + + @Test + public void testGetSupportedBuiltInNames() { + Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build(); + + Set<String> allNames = cfg.getSupportedBuiltInNames(ParsingConfiguration.AUTO_DETECT_NAMING_CONVENTION); + Set<String> lNames = cfg.getSupportedBuiltInNames(ParsingConfiguration.LEGACY_NAMING_CONVENTION); + Set<String> cNames = cfg.getSupportedBuiltInNames(ParsingConfiguration.CAMEL_CASE_NAMING_CONVENTION); + + checkNamingConventionNameSets(allNames, lNames, cNames); + } + + private void checkNamingConventionNameSets(Set<String> allNames, Set<String> lNames, Set<String> cNames) { + for (String name : lNames) { + assertThat(allNames, hasItem(name)); + assertTrue("Should be all-lowercase: " + name, name.equals(name.toLowerCase())); + } + for (String name : cNames) { + assertThat(allNames, hasItem(name)); + } + for (String name : allNames) { + assertThat(name, anyOf(isIn(lNames), isIn(cNames))); + } + assertEquals(lNames.size(), cNames.size()); + } + + @Test + public void testRemovedSettings() { + Configuration.Builder cfgB = new Configuration.Builder(Configuration.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"))); + } + } + + @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 boolean keyFieldExists(String name) throws Exception { + Field field; + try { + field = Configuration.class.getField(name.toUpperCase() + "_KEY"); + } catch (NoSuchFieldException e) { + return false; + } + assertEquals(name, field.get(null)); + return true; + } + + private static class MyScalarModel implements TemplateScalarModel { + + @Override + public String getAsString() throws TemplateModelException { + return "my"; + } + + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java new file mode 100644 index 0000000..6714fc3 --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CoreLocaleUtilsTest.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.freemarker.core; + +import static org.junit.Assert.*; + +import java.util.Locale; + +import org.apache.freemarker.core.util._LocaleUtil; +import org.junit.Test; + +public class CoreLocaleUtilsTest { + + @Test + public void testGetLessSpecificLocale() { + Locale locale; + + locale = new Locale("ru", "RU", "Linux"); + assertEquals("ru_RU_Linux", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("ru_RU", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("ru", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("ch", "CH"); + assertEquals("ch_CH", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("ch", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("ja"); + assertEquals("ja", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("ja", "", ""); + assertEquals("ja", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale(""); + assertEquals("", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + + locale = new Locale("hu", "", "Linux"); + assertEquals("hu__Linux", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertEquals("hu", locale.toString()); + locale = _LocaleUtil.getLessSpecificLocale(locale); + assertNull(locale); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/28a276c8/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java new file mode 100644 index 0000000..726a20c --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/CustomAttributeTest.java @@ -0,0 +1,163 @@ +/* + * 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 static org.junit.Assert.*; + +import java.math.BigDecimal; +import java.util.Arrays; + +import org.junit.Test; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +@SuppressWarnings("boxing") +public class CustomAttributeTest { + + private static final String KEY_1 = "key1"; + private static final String KEY_2 = "key2"; + private static final String KEY_3 = "key3"; + private static final Integer KEY_4 = 4; + + private static final Integer VALUE_1 = 1; // Serializable + private static final Object VALUE_2 = new Object(); + private static final Object VALUE_3 = new Object(); + private static final Object VALUE_4 = new Object(); + private static final Object VALUE_LIST = ImmutableList.<Object>of( + "s", BigDecimal.valueOf(2), Boolean.TRUE, ImmutableMap.of("a", "A")); + private static final Object VALUE_BIGDECIMAL = BigDecimal.valueOf(22); + + private static final Object CUST_ATT_KEY = new Object(); + + @Test + public void testStringKey() throws Exception { + // Need some MutableProcessingConfiguration: + TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder(); + + assertEquals(0, mpc.getCustomAttributeNames().length); + assertNull(mpc.getCustomAttribute(KEY_1)); + + mpc.setCustomAttribute(KEY_1, VALUE_1); + assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames()); + assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1)); + + mpc.setCustomAttribute(KEY_2, VALUE_2); + assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames())); + assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + + mpc.setCustomAttribute(KEY_1, VALUE_2); + assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames())); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + + mpc.setCustomAttribute(KEY_1, null); + assertArrayEquals(new String[] { KEY_1, KEY_2 }, sort(mpc.getCustomAttributeNames())); + assertNull(mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + + mpc.removeCustomAttribute(KEY_1); + assertArrayEquals(new String[] { KEY_2 }, mpc.getCustomAttributeNames()); + assertNull(mpc.getCustomAttribute(KEY_1)); + assertSame(VALUE_2, mpc.getCustomAttribute(KEY_2)); + } + + @Test + public void testRemoveFromEmptySet() throws Exception { + // Need some MutableProcessingConfiguration: + TemplateConfiguration.Builder mpc = new TemplateConfiguration.Builder(); + + mpc.removeCustomAttribute(KEY_1); + assertEquals(0, mpc.getCustomAttributeNames().length); + assertNull(mpc.getCustomAttribute(KEY_1)); + + mpc.setCustomAttribute(KEY_1, VALUE_1); + assertArrayEquals(new String[] { KEY_1 }, mpc.getCustomAttributeNames()); + assertSame(VALUE_1, mpc.getCustomAttribute(KEY_1)); + } + + @Test + public void testAttrsFromFtlHeaderOnly() throws Exception { + Template t = new Template(null, "<#ftl attributes={" + + "'" + KEY_1 + "': [ 's', 2, true, { 'a': 'A' } ], " + + "'" + KEY_2 + "': " + VALUE_BIGDECIMAL + " " + + "}>", + new Configuration.Builder(Configuration.VERSION_3_0_0).build()); + + assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet()); + assertEquals(VALUE_LIST, t.getCustomAttribute(KEY_1)); + assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2)); + + t.setCustomAttribute(KEY_1, VALUE_1); + assertEquals(VALUE_1, t.getCustomAttribute(KEY_1)); + assertEquals(VALUE_BIGDECIMAL, t.getCustomAttribute(KEY_2)); + + t.setCustomAttribute(KEY_1, null); + assertEquals(ImmutableSet.of(KEY_1, KEY_2), t.getCustomAttributes().keySet()); + assertNull(t.getCustomAttribute(KEY_1)); + } + + @Test + public void testAttrsFromFtlHeaderAndFromTemplateConfiguration() throws Exception { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setCustomAttribute(KEY_3, VALUE_3); + tcb.setCustomAttribute(KEY_4, VALUE_4); + Template t = new Template(null, "<#ftl attributes={" + + "'" + KEY_1 + "': 'a', " + + "'" + KEY_2 + "': 'b', " + + "'" + KEY_3 + "': 'c' " + + "}>", + new Configuration.Builder(Configuration.VERSION_3_0_0).build(), + tcb.build()); + + assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet()); + assertEquals("a", t.getCustomAttribute(KEY_1)); + assertEquals("b", t.getCustomAttribute(KEY_2)); + assertEquals("c", t.getCustomAttribute(KEY_3)); // Has overridden TC attribute + assertEquals(VALUE_4, t.getCustomAttribute(KEY_4)); // Inherited TC attribute + + t.setCustomAttribute(KEY_3, null); + assertEquals(ImmutableSet.of(KEY_1, KEY_2, KEY_3, KEY_4), t.getCustomAttributes().keySet()); + assertNull("null value shouldn't cause fallback to TC attribute", t.getCustomAttribute(KEY_3)); + } + + + @Test + public void testAttrsFromTemplateConfigurationOnly() throws Exception { + TemplateConfiguration.Builder tcb = new TemplateConfiguration.Builder(); + tcb.setCustomAttribute(KEY_3, VALUE_3); + tcb.setCustomAttribute(KEY_4, VALUE_4); + Template t = new Template(null, "", + new Configuration.Builder(Configuration.VERSION_3_0_0).build(), + tcb.build()); + + assertEquals(ImmutableSet.of(KEY_3, KEY_4), t.getCustomAttributes().keySet()); + assertEquals(VALUE_3, t.getCustomAttribute(KEY_3)); + assertEquals(VALUE_4, t.getCustomAttribute(KEY_4)); + } + + private Object[] sort(String[] customAttributeNames) { + Arrays.sort(customAttributeNames); + return customAttributeNames; + } + +}
