blob: 99b1b7acde957a5e48247f30869d334dca3d32a7 [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 freemarker.core;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.TimeZone;
import org.junit.Test;
import com.google.common.collect.ImmutableMap;
import freemarker.template.Configuration;
import freemarker.template.SimpleObjectWrapper;
import freemarker.template.Template;
import freemarker.template.TemplateException;
import freemarker.template.TemplateExceptionHandler;
import freemarker.template.Version;
import freemarker.template.utility.NullArgumentException;
@SuppressWarnings("boxing")
public class TemplateConfigurationTest {
private final class DummyArithmeticEngine extends ArithmeticEngine {
@Override
public int compareNumbers(Number first, Number second) throws TemplateException {
return 0;
}
@Override
public Number add(Number first, Number second) throws TemplateException {
return 22;
}
@Override
public Number subtract(Number first, Number second) throws TemplateException {
return null;
}
@Override
public Number multiply(Number first, Number second) throws TemplateException {
return 33;
}
@Override
public Number divide(Number first, Number second) throws TemplateException {
return null;
}
@Override
public Number modulus(Number first, Number second) throws TemplateException {
return null;
}
@Override
public Number toNumber(String s) {
return 11;
}
}
private static final Version ICI = Configuration.VERSION_2_3_22;
private static final Configuration DEFAULT_CFG = new Configuration(ICI);
private static final TimeZone NON_DEFAULT_TZ;
static {
TimeZone defaultTZ = DEFAULT_CFG.getTimeZone();
TimeZone tz = TimeZone.getTimeZone("UTC");
if (tz.equals(defaultTZ)) {
tz = TimeZone.getTimeZone("GMT+01");
if (tz.equals(defaultTZ)) {
throw new AssertionError("Couldn't chose a non-default time zone");
}
}
NON_DEFAULT_TZ = tz;
}
private static final Locale NON_DEFAULT_LOCALE;
static {
Locale defaultLocale = DEFAULT_CFG.getLocale();
Locale locale = Locale.GERMAN;
if (locale.equals(defaultLocale)) {
locale = Locale.US;
if (locale.equals(defaultLocale)) {
throw new AssertionError("Couldn't chose a non-default locale");
}
}
NON_DEFAULT_LOCALE = locale;
}
private static final String NON_DEFAULT_ENCODING;
static {
String defaultEncoding = DEFAULT_CFG.getDefaultEncoding();
String encoding = "UTF-16";
if (encoding.equals(defaultEncoding)) {
encoding = "UTF-8";
if (encoding.equals(defaultEncoding)) {
throw new AssertionError("Couldn't chose a non-default locale");
}
}
NON_DEFAULT_ENCODING = encoding;
}
private static final Map<String, Object> SETTING_ASSIGNMENTS;
static {
SETTING_ASSIGNMENTS = new HashMap<String, Object>();
// "Configurable" settings:
SETTING_ASSIGNMENTS.put("APIBuiltinEnabled", true);
SETTING_ASSIGNMENTS.put("SQLDateAndTimeTimeZone", NON_DEFAULT_TZ);
SETTING_ASSIGNMENTS.put("URLEscapingCharset", "utf-16");
SETTING_ASSIGNMENTS.put("autoFlush", false);
SETTING_ASSIGNMENTS.put("booleanFormat", "J,N");
SETTING_ASSIGNMENTS.put("classicCompatibleAsInt", 2);
SETTING_ASSIGNMENTS.put("dateFormat", "yyyy-#DDD");
SETTING_ASSIGNMENTS.put("dateTimeFormat", "yyyy-#DDD-@HH:mm");
SETTING_ASSIGNMENTS.put("locale", NON_DEFAULT_LOCALE);
SETTING_ASSIGNMENTS.put("logTemplateExceptions", false);
SETTING_ASSIGNMENTS.put("newBuiltinClassResolver", TemplateClassResolver.ALLOWS_NOTHING_RESOLVER);
SETTING_ASSIGNMENTS.put("numberFormat", "0.0000");
SETTING_ASSIGNMENTS.put("objectWrapper", new SimpleObjectWrapper(ICI));
SETTING_ASSIGNMENTS.put("outputEncoding", "utf-16");
SETTING_ASSIGNMENTS.put("showErrorTips", false);
SETTING_ASSIGNMENTS.put("templateExceptionHandler", TemplateExceptionHandler.IGNORE_HANDLER);
SETTING_ASSIGNMENTS.put("timeFormat", "@HH:mm");
SETTING_ASSIGNMENTS.put("timeZone", NON_DEFAULT_TZ);
SETTING_ASSIGNMENTS.put("arithmeticEngine", ArithmeticEngine.CONSERVATIVE_ENGINE);
SETTING_ASSIGNMENTS.put("customNumberFormats",
ImmutableMap.of("dummy", HexTemplateNumberFormatFactory.INSTANCE));
SETTING_ASSIGNMENTS.put("customDateFormats",
ImmutableMap.of("dummy", EpochMillisTemplateDateFormatFactory.INSTANCE));
// Parser-only settings:
SETTING_ASSIGNMENTS.put("tagSyntax", Configuration.SQUARE_BRACKET_TAG_SYNTAX);
SETTING_ASSIGNMENTS.put("namingConvention", Configuration.LEGACY_NAMING_CONVENTION);
SETTING_ASSIGNMENTS.put("whitespaceStripping", false);
SETTING_ASSIGNMENTS.put("strictSyntaxMode", false);
SETTING_ASSIGNMENTS.put("autoEscapingPolicy", Configuration.DISABLE_AUTO_ESCAPING_POLICY);
SETTING_ASSIGNMENTS.put("outputFormat", HTMLOutputFormat.INSTANCE);
SETTING_ASSIGNMENTS.put("recognizeStandardFileExtensions", true);
// Special settings:
SETTING_ASSIGNMENTS.put("encoding", NON_DEFAULT_ENCODING);
}
public static String getIsSetMethodName(String readMethodName) {
String isSetMethodName = (readMethodName.startsWith("get") ? "is" + readMethodName.substring(3)
: readMethodName)
+ "Set";
if (isSetMethodName.equals("isClassicCompatibleAsIntSet")) {
isSetMethodName = "isClassicCompatibleSet";
}
return isSetMethodName;
}
public static List<PropertyDescriptor> getTemplateConfigurationSettingPropDescs(
boolean includeCompilerSettings, boolean includeSpecialSettings)
throws IntrospectionException {
List<PropertyDescriptor> settingPropDescs = new ArrayList<PropertyDescriptor>();
BeanInfo beanInfo = Introspector.getBeanInfo(TemplateConfiguration.class);
for (PropertyDescriptor pd : beanInfo.getPropertyDescriptors()) {
String name = pd.getName();
if (pd.getWriteMethod() != null && !IGNORED_PROP_NAMES.contains(name)
&& (includeCompilerSettings
|| (CONFIGURABLE_PROP_NAMES.contains(name) || !PARSER_PROP_NAMES.contains(name)))
&& (includeSpecialSettings
|| !SPECIAL_PROP_NAMES.contains(name))) {
if (pd.getReadMethod() == null) {
throw new AssertionError("Property has no read method: " + pd);
}
settingPropDescs.add(pd);
}
}
Collections.sort(settingPropDescs, new Comparator<PropertyDescriptor>() {
public int compare(PropertyDescriptor o1, PropertyDescriptor o2) {
return o1.getName().compareToIgnoreCase(o2.getName());
}
});
return settingPropDescs;
}
private static final Set<String> IGNORED_PROP_NAMES;
static {
IGNORED_PROP_NAMES = new HashSet();
IGNORED_PROP_NAMES.add("class");
IGNORED_PROP_NAMES.add("strictBeanModels");
IGNORED_PROP_NAMES.add("parentConfiguration");
IGNORED_PROP_NAMES.add("settings");
IGNORED_PROP_NAMES.add("classicCompatible");
}
private static final Set<String> CONFIGURABLE_PROP_NAMES;
static {
CONFIGURABLE_PROP_NAMES = new HashSet<String>();
try {
for (PropertyDescriptor propDesc : Introspector.getBeanInfo(Configurable.class).getPropertyDescriptors()) {
String propName = propDesc.getName();
if (!IGNORED_PROP_NAMES.contains(propName)) {
CONFIGURABLE_PROP_NAMES.add(propName);
}
}
} catch (IntrospectionException e) {
throw new IllegalStateException("Failed to init static field", e);
}
}
private static final Set<String> PARSER_PROP_NAMES;
static {
PARSER_PROP_NAMES = new HashSet<String>();
// It's an interface; can't use standard Inrospector
for (Method m : ParserConfiguration.class.getMethods()) {
String propertyName;
if (m.getName().startsWith("get")) {
propertyName = m.getName().substring(3);
} else if (m.getName().startsWith("is")) {
propertyName = m.getName().substring(2);
} else {
propertyName = null;
}
if (propertyName != null) {
if (!Character.isUpperCase(propertyName.charAt(1))) {
propertyName = Character.toLowerCase(propertyName.charAt(0)) + propertyName.substring(1);
}
PARSER_PROP_NAMES.add(propertyName);
}
}
}
private static final Set<String> SPECIAL_PROP_NAMES;
static {
SPECIAL_PROP_NAMES = new HashSet<String>();
SPECIAL_PROP_NAMES.add("encoding");
}
private static final CustomAttribute CA1 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
private static final CustomAttribute CA2 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
private static final CustomAttribute CA3 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
private static final CustomAttribute CA4 = new CustomAttribute(CustomAttribute.SCOPE_TEMPLATE);
@Test
public void testMergeBasicFunctionality() throws Exception {
for (PropertyDescriptor propDesc1 : getTemplateConfigurationSettingPropDescs(true, true)) {
for (PropertyDescriptor propDesc2 : getTemplateConfigurationSettingPropDescs(true, true)) {
TemplateConfiguration tc1 = new TemplateConfiguration();
TemplateConfiguration tc2 = new TemplateConfiguration();
Object value1 = SETTING_ASSIGNMENTS.get(propDesc1.getName());
propDesc1.getWriteMethod().invoke(tc1, value1);
Object value2 = SETTING_ASSIGNMENTS.get(propDesc2.getName());
propDesc2.getWriteMethod().invoke(tc2, value2);
tc1.merge(tc2);
Object mValue1 = propDesc1.getReadMethod().invoke(tc1);
Object mValue2 = propDesc2.getReadMethod().invoke(tc1);
assertEquals("For " + propDesc1.getName(), value1, mValue1);
assertEquals("For " + propDesc2.getName(), value2, mValue2);
}
}
}
@Test
public void testMergeMapSettings() throws Exception {
TemplateConfiguration tc1 = new TemplateConfiguration();
tc1.setCustomDateFormats(ImmutableMap.of(
"epoch", EpochMillisTemplateDateFormatFactory.INSTANCE,
"x", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE));
tc1.setCustomNumberFormats(ImmutableMap.of(
"hex", HexTemplateNumberFormatFactory.INSTANCE,
"x", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE));
TemplateConfiguration tc2 = new TemplateConfiguration();
tc2.setCustomDateFormats(ImmutableMap.of(
"loc", LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE,
"x", EpochMillisDivTemplateDateFormatFactory.INSTANCE));
tc2.setCustomNumberFormats(ImmutableMap.of(
"loc", LocaleSensitiveTemplateNumberFormatFactory.INSTANCE,
"x", BaseNTemplateNumberFormatFactory.INSTANCE));
tc1.merge(tc2);
Map<String, ? extends TemplateDateFormatFactory> mergedCustomDateFormats = tc1.getCustomDateFormats();
assertEquals(EpochMillisTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("epoch"));
assertEquals(LocAndTZSensitiveTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("loc"));
assertEquals(EpochMillisDivTemplateDateFormatFactory.INSTANCE, mergedCustomDateFormats.get("x"));
Map<String, ? extends TemplateNumberFormatFactory> mergedCustomNumberFormats = tc1.getCustomNumberFormats();
assertEquals(HexTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("hex"));
assertEquals(LocaleSensitiveTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("loc"));
assertEquals(BaseNTemplateNumberFormatFactory.INSTANCE, mergedCustomNumberFormats.get("x"));
// Empty map merging optimization:
tc1.merge(new TemplateConfiguration());
assertSame(mergedCustomDateFormats, tc1.getCustomDateFormats());
assertSame(mergedCustomNumberFormats, tc1.getCustomNumberFormats());
// Empty map merging optimization:
TemplateConfiguration tc3 = new TemplateConfiguration();
tc3.merge(tc1);
assertSame(mergedCustomDateFormats, tc3.getCustomDateFormats());
assertSame(mergedCustomNumberFormats, tc3.getCustomNumberFormats());
}
@Test
public void testMergePriority() throws Exception {
TemplateConfiguration tc1 = new TemplateConfiguration();
tc1.setDateFormat("1");
tc1.setTimeFormat("1");
tc1.setDateTimeFormat("1");
TemplateConfiguration tc2 = new TemplateConfiguration();
tc2.setDateFormat("2");
tc2.setTimeFormat("2");
TemplateConfiguration tc3 = new TemplateConfiguration();
tc3.setDateFormat("3");
tc1.merge(tc2);
tc1.merge(tc3);
assertEquals("3", tc1.getDateFormat());
assertEquals("2", tc1.getTimeFormat());
assertEquals("1", tc1.getDateTimeFormat());
}
@Test
public void testMergeCustomAttributes() throws Exception {
TemplateConfiguration tc1 = new TemplateConfiguration();
tc1.setCustomAttribute("k1", "v1");
tc1.setCustomAttribute("k2", "v1");
tc1.setCustomAttribute("k3", "v1");
CA1.set("V1", tc1);
CA2.set("V1", tc1);
CA3.set("V1", tc1);
TemplateConfiguration tc2 = new TemplateConfiguration();
tc2.setCustomAttribute("k1", "v2");
tc2.setCustomAttribute("k2", "v2");
CA1.set("V2", tc2);
CA2.set("V2", tc2);
TemplateConfiguration tc3 = new TemplateConfiguration();
tc3.setCustomAttribute("k1", "v3");
CA1.set("V3", tc2);
tc1.merge(tc2);
tc1.merge(tc3);
assertEquals("v3", tc1.getCustomAttribute("k1"));
assertEquals("v2", tc1.getCustomAttribute("k2"));
assertEquals("v1", tc1.getCustomAttribute("k3"));
assertEquals("V3", CA1.get(tc1));
assertEquals("V2", CA2.get(tc1));
assertEquals("V1", CA3.get(tc1));
}
@Test
public void testMergeNullCustomAttributes() throws Exception {
TemplateConfiguration tc1 = new TemplateConfiguration();
tc1.setCustomAttribute("k1", "v1");
tc1.setCustomAttribute("k2", "v1");
tc1.setCustomAttribute(null, "v1");
CA1.set("V1", tc1);
CA2.set("V1", tc1);
CA3.set(null, tc1);
assertEquals("v1", tc1.getCustomAttribute("k1"));
assertEquals("v1", tc1.getCustomAttribute("k2"));
assertNull("v1", tc1.getCustomAttribute("k3"));
assertEquals("V1", CA1.get(tc1));
assertEquals("V1", CA2.get(tc1));
assertNull(CA3.get(tc1));
TemplateConfiguration tc2 = new TemplateConfiguration();
tc2.setCustomAttribute("k1", "v2");
tc2.setCustomAttribute("k2", null);
CA1.set("V2", tc2);
CA2.set(null, tc2);
TemplateConfiguration tc3 = new TemplateConfiguration();
tc3.setCustomAttribute("k1", null);
CA1.set(null, tc2);
tc1.merge(tc2);
tc1.merge(tc3);
assertNull(tc1.getCustomAttribute("k1"));
assertNull(tc1.getCustomAttribute("k2"));
assertNull(tc1.getCustomAttribute("k3"));
assertNull(CA1.get(tc1));
assertNull(CA2.get(tc1));
assertNull(CA3.get(tc1));
TemplateConfiguration tc4 = new TemplateConfiguration();
tc4.setCustomAttribute("k1", "v4");
CA1.set("V4", tc4);
tc1.merge(tc4);
assertEquals("v4", tc1.getCustomAttribute("k1"));
assertNull(tc1.getCustomAttribute("k2"));
assertNull(tc1.getCustomAttribute("k3"));
assertEquals("V4", CA1.get(tc1));
assertNull(CA2.get(tc1));
assertNull(CA3.get(tc1));
}
@Test
public void testConfigureNonParserConfig() throws Exception {
for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(false, true)) {
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
Object newValue = SETTING_ASSIGNMENTS.get(pd.getName());
pd.getWriteMethod().invoke(tc, newValue);
Template t = new Template(null, "", DEFAULT_CFG);
Method tReaderMethod = t.getClass().getMethod(pd.getReadMethod().getName());
assertNotEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
tc.apply(t);
assertEquals("For \"" + pd.getName() + "\"", newValue, tReaderMethod.invoke(t));
}
}
@Test
public void testConfigureCustomAttributes() throws Exception {
Configuration cfg = new Configuration(Configuration.VERSION_2_3_22);
cfg.setCustomAttribute("k1", "c");
cfg.setCustomAttribute("k2", "c");
cfg.setCustomAttribute("k3", "c");
TemplateConfiguration tc = new TemplateConfiguration();
tc.setCustomAttribute("k2", "tc");
tc.setCustomAttribute("k3", null);
tc.setCustomAttribute("k4", "tc");
tc.setCustomAttribute("k5", "tc");
tc.setCustomAttribute("k6", "tc");
CA1.set("tc", tc);
CA2.set("tc", tc);
CA3.set("tc", tc);
Template t = new Template(null, "", cfg);
t.setCustomAttribute("k5", "t");
t.setCustomAttribute("k6", null);
t.setCustomAttribute("k7", "t");
CA2.set("t", t);
CA3.set(null, t);
CA4.set("t", t);
tc.setParentConfiguration(cfg);
tc.apply(t);
assertEquals("c", t.getCustomAttribute("k1"));
assertEquals("tc", t.getCustomAttribute("k2"));
assertNull(t.getCustomAttribute("k3"));
assertEquals("tc", t.getCustomAttribute("k4"));
assertEquals("t", t.getCustomAttribute("k5"));
assertNull(t.getCustomAttribute("k6"));
assertEquals("t", t.getCustomAttribute("k7"));
assertEquals("tc", CA1.get(t));
assertEquals("t", CA2.get(t));
assertNull(CA3.get(t));
assertEquals("t", CA4.get(t));
}
@Test
public void testConfigureParser() throws Exception {
Set<String> testedProps = new HashSet<String>();
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setTagSyntax(Configuration.SQUARE_BRACKET_TAG_SYNTAX);
assertOutputWithoutAndWithTC(tc, "[#if true]y[/#if]", "[#if true]y[/#if]", "y");
testedProps.add(Configuration.TAG_SYNTAX_KEY_CAMEL_CASE);
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertOutputWithoutAndWithTC(tc, "<#if true>y<#elseif false>n</#if>", "y", null);
testedProps.add(Configuration.NAMING_CONVENTION_KEY_CAMEL_CASE);
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setWhitespaceStripping(false);
assertOutputWithoutAndWithTC(tc, "<#if true>\nx\n</#if>\n", "x\n", "\nx\n\n");
testedProps.add(Configuration.WHITESPACE_STRIPPING_KEY_CAMEL_CASE);
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setArithmeticEngine(new DummyArithmeticEngine());
assertOutputWithoutAndWithTC(tc, "${1} ${1+1}", "1 2", "11 22");
testedProps.add(Configuration.ARITHMETIC_ENGINE_KEY_CAMEL_CASE);
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setOutputFormat(XMLOutputFormat.INSTANCE);
assertOutputWithoutAndWithTC(tc, "${.outputFormat} ${\"a'b\"}",
UndefinedOutputFormat.INSTANCE.getName() + " a'b",
XMLOutputFormat.INSTANCE.getName() + " a&apos;b");
testedProps.add(Configuration.OUTPUT_FORMAT_KEY_CAMEL_CASE);
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setOutputFormat(XMLOutputFormat.INSTANCE);
tc.setAutoEscapingPolicy(Configuration.DISABLE_AUTO_ESCAPING_POLICY);
assertOutputWithoutAndWithTC(tc, "${'a&b'}", "a&b", "a&b");
testedProps.add(Configuration.AUTO_ESCAPING_POLICY_KEY_CAMEL_CASE);
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setStrictSyntaxMode(false);
assertOutputWithoutAndWithTC(tc, "<if true>y</if>", "<if true>y</if>", "y");
testedProps.add("strictSyntaxMode");
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(new Configuration(new Version(2, 3, 0)));
assertOutputWithoutAndWithTC(tc, "<#foo>", null, "<#foo>");
testedProps.add(Configuration.INCOMPATIBLE_IMPROVEMENTS_KEY_CAMEL_CASE);
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(new Configuration(new Version(2, 3, 0)));
tc.setRecognizeStandardFileExtensions(true);
assertOutputWithoutAndWithTC(tc, "${.outputFormat}",
UndefinedOutputFormat.INSTANCE.getName(), HTMLOutputFormat.INSTANCE.getName());
testedProps.add(Configuration.RECOGNIZE_STANDARD_FILE_EXTENSIONS_KEY_CAMEL_CASE);
}
assertEquals("Check that you have tested all parser settings; ", PARSER_PROP_NAMES, testedProps);
}
@Test
public void testConfigureParserTooLowIcI() throws Exception {
Configuration cfgWithTooLowIcI = new Configuration(Configuration.VERSION_2_3_21);
for (PropertyDescriptor propDesc : getTemplateConfigurationSettingPropDescs(true, false)) {
TemplateConfiguration tc = new TemplateConfiguration();
String propName = propDesc.getName();
Object value = SETTING_ASSIGNMENTS.get(propName);
propDesc.getWriteMethod().invoke(tc, value);
boolean shouldFail;
if (CONFIGURABLE_PROP_NAMES.contains(propName)) {
shouldFail = true;
} else if (PARSER_PROP_NAMES.contains(propName)) {
shouldFail = false;
} else {
fail("Uncategorized property: " + propName);
return;
}
try {
tc.setParentConfiguration(cfgWithTooLowIcI);
if (shouldFail) {
fail("Should fail with property: " + propName);
}
} catch (IllegalStateException e) {
if (!shouldFail) {
throw e;
}
assertThat(e.getMessage(), containsString("2.3.22"));
}
}
}
@Test
public void testArithmeticEngine() throws TemplateException, IOException {
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setArithmeticEngine(new DummyArithmeticEngine());
assertOutputWithoutAndWithTC(tc,
"<#setting locale='en_US'>${1} ${1+1} ${1*3} <#assign x = 1>${x + x} ${x * 3}",
"1 2 3 2 3", "11 22 33 22 33");
// Doesn't affect template.arithmeticEngine, only affects the parsing:
Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null);
assertEquals(DEFAULT_CFG.getArithmeticEngine(), t.getArithmeticEngine());
}
@Test
public void testStringInterpolate() throws TemplateException, IOException {
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setArithmeticEngine(new DummyArithmeticEngine());
assertOutputWithoutAndWithTC(tc,
"<#setting locale='en_US'>${'${1} ${1+1} ${1*3}'} <#assign x = 1>${'${x + x} ${x * 3}'}",
"1 2 3 2 3", "11 22 33 22 33");
// Doesn't affect template.arithmeticEngine, only affects the parsing:
Template t = new Template(null, null, new StringReader(""), DEFAULT_CFG, tc, null);
assertEquals(DEFAULT_CFG.getArithmeticEngine(), t.getArithmeticEngine());
}
@Test
public void testInterpret() throws TemplateException, IOException {
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setArithmeticEngine(new DummyArithmeticEngine());
assertOutputWithoutAndWithTC(tc,
"<#setting locale='en_US'><#assign src = r'${1} <#assign x = 1>${x + x}'><@src?interpret />",
"1 2", "11 22");
tc.setWhitespaceStripping(false);
assertOutputWithoutAndWithTC(tc,
"<#if true>\nX</#if><#assign src = r'<#if true>\nY</#if>'><@src?interpret />",
"XY", "\nX\nY");
}
@Test
public void testEval() throws TemplateException, IOException {
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
tc.setArithmeticEngine(new DummyArithmeticEngine());
assertOutputWithoutAndWithTC(tc,
"<#assign x = 1>${r'1 + x'?eval?c}",
"2", "22");
assertOutputWithoutAndWithTC(tc,
"${r'1?c'?eval}",
"1", "11");
}
{
TemplateConfiguration tc = new TemplateConfiguration();
tc.setParentConfiguration(DEFAULT_CFG);
String outputEncoding = "ISO-8859-2";
tc.setOutputEncoding(outputEncoding);
String legacyNCFtl = "${r'.output_encoding!\"null\"'?eval}";
String camelCaseNCFtl = "${r'.outputEncoding!\"null\"'?eval}";
// Default is re-auto-detecting in ?eval:
assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding);
assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding);
// Force camelCase:
tc.setNamingConvention(Configuration.CAMEL_CASE_NAMING_CONVENTION);
assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", null);
assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", outputEncoding);
// Force legacy:
tc.setNamingConvention(Configuration.LEGACY_NAMING_CONVENTION);
assertOutputWithoutAndWithTC(tc, legacyNCFtl, "null", outputEncoding);
assertOutputWithoutAndWithTC(tc, camelCaseNCFtl, "null", null);
}
}
@Test
public void testSetParentConfiguration() throws IOException {
TemplateConfiguration tc = new TemplateConfiguration();
Template t = new Template(null, "", DEFAULT_CFG);
try {
tc.apply(t);
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("Configuration"));
}
tc.setParent(DEFAULT_CFG);
try {
tc.setParentConfiguration(new Configuration());
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("Configuration"));
}
try {
// Same as setParentConfiguration
tc.setParent(new Configuration());
fail();
} catch (IllegalStateException e) {
assertThat(e.getMessage(), containsString("Configuration"));
}
try {
tc.setParentConfiguration(null);
fail();
} catch (NullArgumentException e) {
// exected
}
tc.setParent(DEFAULT_CFG);
tc.apply(t);
}
private void assertOutputWithoutAndWithTC(TemplateConfiguration tc, String ftl, String expectedDefaultOutput,
String expectedConfiguredOutput) throws TemplateException, IOException {
assertOutput(tc, ftl, expectedConfiguredOutput);
assertOutput(null, ftl, expectedDefaultOutput);
}
private void assertOutput(TemplateConfiguration tc, String ftl, String expectedConfiguredOutput)
throws TemplateException, IOException {
StringWriter sw = new StringWriter();
try {
Configuration cfg = tc != null ? tc.getParentConfiguration() : DEFAULT_CFG;
Template t = new Template("adhoc.ftlh", null, new StringReader(ftl), cfg, tc, null);
if (tc != null) {
tc.apply(t);
}
t.process(null, sw);
if (expectedConfiguredOutput == null) {
fail("Template should have fail.");
}
} catch (TemplateException e) {
if (expectedConfiguredOutput != null) {
throw e;
}
} catch (ParseException e) {
if (expectedConfiguredOutput != null) {
throw e;
}
}
if (expectedConfiguredOutput != null) {
assertEquals(expectedConfiguredOutput, sw.toString());
}
}
@Test
public void testIsSet() throws Exception {
for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
TemplateConfiguration tc = new TemplateConfiguration();
checkAllIsSetFalseExcept(tc, null);
pd.getWriteMethod().invoke(tc, SETTING_ASSIGNMENTS.get(pd.getName()));
checkAllIsSetFalseExcept(tc, pd.getName());
}
}
private void checkAllIsSetFalseExcept(TemplateConfiguration tc, String setSetting)
throws SecurityException, IntrospectionException,
IllegalArgumentException, IllegalAccessException, InvocationTargetException {
for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
String isSetMethodName = getIsSetMethodName(pd.getReadMethod().getName());
Method isSetMethod;
try {
isSetMethod = TemplateConfiguration.class.getMethod(isSetMethodName);
} catch (NoSuchMethodException e) {
fail("Missing " + isSetMethodName + " method for \"" + pd.getName() + "\".");
return;
}
if (pd.getName().equals(setSetting)) {
assertTrue(isSetMethod + " should return true", (Boolean) (isSetMethod.invoke(tc)));
} else {
assertFalse(isSetMethod + " should return false", (Boolean) (isSetMethod.invoke(tc)));
}
}
}
/**
* Test case self-check.
*/
@Test
public void checkTestAssignments() throws Exception {
for (PropertyDescriptor pd : getTemplateConfigurationSettingPropDescs(true, true)) {
String propName = pd.getName();
if (!SETTING_ASSIGNMENTS.containsKey(propName)) {
fail("Test case doesn't cover all settings in SETTING_ASSIGNMENTS. Missing: " + propName);
}
Method readMethod = pd.getReadMethod();
String cfgMethodName = readMethod.getName();
if (cfgMethodName.equals("getEncoding")) {
// Because Configuration has local-to-encoding map too, this has a different name there.
cfgMethodName = "getDefaultEncoding";
}
Method cfgMethod = DEFAULT_CFG.getClass().getMethod(cfgMethodName, readMethod.getParameterTypes());
Object defaultSettingValue = cfgMethod.invoke(DEFAULT_CFG);
Object assignedValue = SETTING_ASSIGNMENTS.get(propName);
assertNotEquals("SETTING_ASSIGNMENTS must contain a non-default value for " + propName,
assignedValue, defaultSettingValue);
TemplateConfiguration tc = new TemplateConfiguration();
try {
pd.getWriteMethod().invoke(tc, assignedValue);
} catch (Exception e) {
throw new IllegalStateException("For setting \"" + propName + "\" and assigned value of type "
+ (assignedValue != null ? assignedValue.getClass().getName() : "Null"),
e);
}
}
}
}