FREEMARKER-84: Added .get_optional_template
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/51c24766 Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/51c24766 Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/51c24766 Branch: refs/heads/2.3 Commit: 51c2476621809d8f4183f23e894be0106cabe810 Parents: 59f2e7b Author: ddekany <ddek...@apache.org> Authored: Wed Feb 28 20:49:36 2018 +0100 Committer: ddekany <ddek...@apache.org> Committed: Wed Feb 28 20:49:36 2018 +0100 ---------------------------------------------------------------------- .../java/freemarker/core/BuiltinVariable.java | 11 +- .../core/GetOptionalTemplateMethod.java | 202 +++++++++++++++++++ src/manual/en_US/book.xml | 146 +++++++++++++- .../core/GetOptionalTemplateTest.java | 179 ++++++++++++++++ .../template/utility/TemplateModelUtilTest.java | 35 ++-- 5 files changed, 556 insertions(+), 17 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51c24766/src/main/java/freemarker/core/BuiltinVariable.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/BuiltinVariable.java b/src/main/java/freemarker/core/BuiltinVariable.java index 41286dd..1b7617c 100644 --- a/src/main/java/freemarker/core/BuiltinVariable.java +++ b/src/main/java/freemarker/core/BuiltinVariable.java @@ -72,7 +72,8 @@ final class BuiltinVariable extends Expression { static final String URL_ESCAPING_CHARSET_CC = "urlEscapingCharset"; static final String URL_ESCAPING_CHARSET = "url_escaping_charset"; static final String NOW = "now"; - + static final String GET_OPTIONAL_TEMPLATE = "get_optional_template"; + static final String GET_OPTIONAL_TEMPLATE_CC = "getOptionalTemplate"; static final String[] SPEC_VAR_NAMES = new String[] { AUTO_ESC_CC, AUTO_ESC, @@ -83,6 +84,8 @@ final class BuiltinVariable extends Expression { DATA_MODEL_CC, DATA_MODEL, ERROR, + GET_OPTIONAL_TEMPLATE_CC, + GET_OPTIONAL_TEMPLATE, GLOBALS, INCOMPATIBLE_IMPROVEMENTS_CC, INCOMPATIBLE_IMPROVEMENTS, @@ -239,6 +242,12 @@ final class BuiltinVariable extends Expression { if (name == INCOMPATIBLE_IMPROVEMENTS || name == INCOMPATIBLE_IMPROVEMENTS_CC) { return new SimpleScalar(env.getConfiguration().getIncompatibleImprovements().toString()); } + if (name == GET_OPTIONAL_TEMPLATE) { + return GetOptionalTemplateMethod.INSTANCE; + } + if (name == GET_OPTIONAL_TEMPLATE_CC) { + return GetOptionalTemplateMethod.INSTANCE_CC; + } throw new _MiscTemplateException(this, "Invalid special variable: ", name); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51c24766/src/main/java/freemarker/core/GetOptionalTemplateMethod.java ---------------------------------------------------------------------- diff --git a/src/main/java/freemarker/core/GetOptionalTemplateMethod.java b/src/main/java/freemarker/core/GetOptionalTemplateMethod.java new file mode 100644 index 0000000..090341b --- /dev/null +++ b/src/main/java/freemarker/core/GetOptionalTemplateMethod.java @@ -0,0 +1,202 @@ +/* + * 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 java.io.IOException; +import java.util.List; +import java.util.Map; + +import freemarker.template.MalformedTemplateNameException; +import freemarker.template.SimpleHash; +import freemarker.template.Template; +import freemarker.template.TemplateBooleanModel; +import freemarker.template.TemplateDirectiveBody; +import freemarker.template.TemplateDirectiveModel; +import freemarker.template.TemplateException; +import freemarker.template.TemplateHashModelEx; +import freemarker.template.TemplateHashModelEx2.KeyValuePair; +import freemarker.template.TemplateHashModelEx2.KeyValuePairIterator; +import freemarker.template.TemplateMethodModelEx; +import freemarker.template.TemplateModel; +import freemarker.template.TemplateModelException; +import freemarker.template.TemplateScalarModel; +import freemarker.template.utility.TemplateModelUtils; + +/** + * Implements {@code .get_optional_template(name, options)}. + */ +class GetOptionalTemplateMethod implements TemplateMethodModelEx { + + static final GetOptionalTemplateMethod INSTANCE = new GetOptionalTemplateMethod( + BuiltinVariable.GET_OPTIONAL_TEMPLATE); + static final GetOptionalTemplateMethod INSTANCE_CC = new GetOptionalTemplateMethod( + BuiltinVariable.GET_OPTIONAL_TEMPLATE_CC); + + private static final String OPTION_ENCODING = "encoding"; + private static final String OPTION_PARSE = "parse"; + + private static final String RESULT_INCLUDE = "include"; + private static final String RESULT_IMPORT = "import"; + private static final String RESULT_EXISTS = "exists"; + + /** Used in error messages */ + private final String methodName; + + private GetOptionalTemplateMethod(String builtInVarName) { + this.methodName = "." + builtInVarName; + } + + public Object exec(List args) throws TemplateModelException { + final int argCnt = args.size(); + if (argCnt < 1 || argCnt > 2) { + throw _MessageUtil.newArgCntError(methodName, argCnt, 1, 2); + } + + final Environment env = Environment.getCurrentEnvironment(); + if (env == null) { + throw new IllegalStateException("No freemarer.core.Environment is associated to the current thread."); + } + + final String absTemplateName; + { + TemplateModel arg = (TemplateModel) args.get(0); + if (!(arg instanceof TemplateScalarModel)) { + throw _MessageUtil.newMethodArgMustBeStringException(methodName, 0, arg); + } + String templateName = EvalUtil.modelToString((TemplateScalarModel) arg, null, env); + + try { + absTemplateName = env.toFullTemplateName(env.getCurrentTemplate().getName(), templateName); + } catch (MalformedTemplateNameException e) { + throw new _TemplateModelException( + e, "Failed to convert template path to full path; see cause exception."); + } + } + + final TemplateHashModelEx options; + if (argCnt > 1) { + TemplateModel arg = (TemplateModel) args.get(1); + if (!(arg instanceof TemplateHashModelEx)) { + throw _MessageUtil.newMethodArgMustBeExtendedHashException(methodName, 1, arg); + } + options = (TemplateHashModelEx) arg; + } else { + options = null; + } + + String encoding = null; + boolean parse = true; + if (options != null) { + final KeyValuePairIterator kvpi = TemplateModelUtils.getKeyValuePairIterator(options); + while (kvpi.hasNext()) { + final KeyValuePair kvp = kvpi.next(); + + final String optName; + { + TemplateModel optNameTM = kvp.getKey(); + if (!(optNameTM instanceof TemplateScalarModel)) { + throw _MessageUtil.newMethodArgInvalidValueException(methodName, 1, + "All keys in the options hash must be strings, but found ", + new _DelayedAOrAn(new _DelayedFTLTypeDescription(optNameTM))); + } + optName = ((TemplateScalarModel) optNameTM).getAsString(); + } + + final TemplateModel optValue = kvp.getValue(); + + if (OPTION_ENCODING.equals(optName)) { + encoding = getStringOption(OPTION_ENCODING, optValue); + } else if (OPTION_PARSE.equals(optName)) { + parse = getBooleanOption(OPTION_PARSE, optValue); + } else { + throw _MessageUtil.newMethodArgInvalidValueException(methodName, 1, + "Unsupported option ", new _DelayedJQuote(optName), "; valid names are: ", + new _DelayedJQuote(OPTION_ENCODING), ", ", new _DelayedJQuote(OPTION_PARSE), "."); + } + } + } + + final Template template; + try { + template = env.getTemplateForInclusion(absTemplateName, encoding, parse, true); + } catch (IOException e) { + throw new _TemplateModelException( + "Error when trying to include template ", new _DelayedJQuote(absTemplateName)); + } + + SimpleHash result = new SimpleHash(env.getObjectWrapper()); + result.put(RESULT_EXISTS, template != null); + // If the template is missing, result.include and such will be missing to, so that a default can be + // conveniently provided like in <@optTemp.include!myDefaultMacro />. + if (template != null) { + result.put(RESULT_INCLUDE, new TemplateDirectiveModel() { + public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body) + throws TemplateException, IOException { + if (!params.isEmpty()) { + throw new TemplateException("This directive supports no parameters.", env); + } + if (loopVars.length != 0) { + throw new TemplateException("This directive supports no loop variables.", env); + } + if (body != null) { + throw new TemplateException("This directive supports no nested conetnt.", env); + } + + env.include(template); + } + }); + result.put(RESULT_IMPORT, new TemplateMethodModelEx() { + public Object exec(List args) throws TemplateModelException { + if (!args.isEmpty()) { + throw new TemplateModelException("This method supports no parameters."); + } + + try { + return env.importLib(template, null); + } catch (IOException e) { + throw new _TemplateModelException(e, "Failed to import loaded template; see cause exception"); + } catch (TemplateException e) { + throw new _TemplateModelException(e, "Failed to import loaded template; see cause exception"); + } + } + }); + } + return result; + } + + private boolean getBooleanOption(String optionName, TemplateModel value) throws TemplateModelException { + if (!(value instanceof TemplateBooleanModel)) { + throw _MessageUtil.newMethodArgInvalidValueException(methodName, 1, + "The value of the ", new _DelayedJQuote(optionName), " option must be a boolean, but it was ", + new _DelayedAOrAn(new _DelayedFTLTypeDescription(value)), "."); + } + return ((TemplateBooleanModel) value).getAsBoolean(); + } + + private String getStringOption(String optionName, TemplateModel value) throws TemplateModelException { + if (!(value instanceof TemplateScalarModel)) { + throw _MessageUtil.newMethodArgInvalidValueException(methodName, 1, + "The value of the ", new _DelayedJQuote(optionName), " option must be a string, but it was ", + new _DelayedAOrAn(new _DelayedFTLTypeDescription(value)), "."); + } + return EvalUtil.modelToString((TemplateScalarModel) value, null, null); + } + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51c24766/src/manual/en_US/book.xml ---------------------------------------------------------------------- diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml index 56851f3..7fa85fa 100644 --- a/src/manual/en_US/book.xml +++ b/src/manual/en_US/book.xml @@ -23378,6 +23378,14 @@ There was no specific handler for node y </listitem> <listitem> + <para><literal>get_optional_template</literal>: This is a method + that's used when you need to include or import a template that's + possibly missing, and you need to handle that case on some special + way. <link linkend="ref_specvar_get_optional_template">More + details...</link></para> + </listitem> + + <listitem> <para><literal>pass</literal>: This is a macro that does nothing. It has no parameters. Mostly used as no-op node handler in XML processing.</para> @@ -23432,7 +23440,9 @@ There was no specific handler for node y </listitem> <listitem> - <para><literal>vars</literal>: Expression + <para><indexterm> + <primary>vars</primary> + </indexterm><literal>vars</literal>: Expression <literal>.vars.foo</literal> returns the same variable as expression <literal>foo</literal>. It's useful if for some reasons you have to use square bracket syntax, since that works only for hash sub @@ -23459,6 +23469,140 @@ There was no specific handler for node y 2.3.21-nightly_20140726T151800Z.</para> </listitem> </itemizedlist> + + <simplesect xml:id="ref_specvar_get_optional_template"> + <title>Using get_optional_template</title> + + <indexterm> + <primary>get_optional_template</primary> + </indexterm> + + <indexterm> + <primary>include optional</primary> + </indexterm> + + <indexterm> + <primary>import optional</primary> + </indexterm> + + <para>This special variable is used when you need to include or import + a template that's possibly missing, and you need to handle that case + on some special way. It a method (so you meant to call it) that has + the following parameters:</para> + + <orderedlist> + <listitem> + <para>The name of the template (can be relative or absolute), like + <literal>"/commonds/footer.ftl"</literal>; similar to the first + parameter of the <link + linkend="ref.directive.include"><literal>include</literal> + directive</link>. Required, string.</para> + </listitem> + + <listitem> + <para>An optional hash of options, like <literal>{ 'parse': false, + 'encoding': 'UTF-16BE' }</literal>. The valid keys are + <literal>encoding</literal> and <literal>parse</literal>. The + meaning of these are the same as of the similarly named <link + linkend="ref.directive.include"><literal>include</literal> + directive</link> parameters.</para> + </listitem> + </orderedlist> + + <para>This method returns a hash that contains the following + entries:</para> + + <itemizedlist> + <listitem> + <para><literal>exists</literal>: A boolean that tells if the + template was found.</para> + </listitem> + + <listitem> + <para><literal>include</literal>: A directive that, when called, + includes the template. Calling this directive is similar to + calling the <link + linkend="ref.directive.include"><literal>include</literal> + directive</link>, but of course with this you spare looking up the + template again. This directive has no parameters, nor nested + content. If <literal>exists</literal> is <literal>false</literal>, + this key will be missing; see later how can this be utilized with + <link linkend="dgui_template_exp_missing_default">the + <literal><replaceable>exp</replaceable>!<replaceable>default</replaceable></literal> + operator</link>.</para> + </listitem> + + <listitem> + <para><literal>import</literal>: A method that, when called, + imports the template, and returns the namespace of the imported + template. Calling this method is similar to calling the + <literal>import</literal> directive, but of course with this you + spare looking up the template again, also, it doesn't assign the + namespace to anything, just returns it. The method has no + parameters. If <literal>exists</literal> is + <literal>false</literal>, this key will be missing; see later how + can this be utilized with <link + linkend="dgui_template_exp_missing_default">the + <literal><replaceable>exp</replaceable>!<replaceable>default</replaceable></literal> + operator</link>.</para> + </listitem> + </itemizedlist> + + <para>When this method is called (like + <literal>.get_optional_template('some.ftl')</literal>), it immediately + loads the template if it exists, but doesn't yet process it, so it + doesn't have any visible effect yet. The template will be processed + only when <literal>include</literal> or <literal>import</literal> + members of the returned structure is called. (Of course, when we say + that it loads the template, it actually looks it up in the template + cache, just like the <link + linkend="ref.directive.include"><literal>include</literal> + directive</link> does.)</para> + + <para>While it's not an error if the template is missing, it's an + error if it does exist but still can't be loaded due to syntactical + errors in it, or due to some I/O error.</para> + + <para>Example, in which depending on if <literal>some.ftl</literal> + exists, we either print <quote>Template was found:</quote> and the + include the template as <literal><#include 'some.ftl'></literal> + would, otherwise it we print <quote>Template was + missing.</quote>:</para> + + <programlisting role="template"><#assign optTemp = .get_optional_template('some.ftl')> +<#if optTemp.exists> + Template was found: + <@optTemp.include /> +<#else> + Template was missing. +</#if></programlisting> + + <para>Example, in which we try to include <literal>some.ftl</literal>, + but if that's missing then we try to include + <literal>some-fallback.ftl</literal>, and if that's missing too then + we call the <literal>ultimateFallback</literal> macro instead of + including anything (note the <literal>!</literal>-s after the + <literal>include</literal>-s; they belong to <link + linkend="dgui_template_exp_missing_default">the + <literal><replaceable>exp</replaceable>!<replaceable>default</replaceable></literal> + operator</link>):</para> + + <programlisting role="template"><#macro ultimateFallback> + Something +</#macro> + +<@( + .get_optional_template('some.ftl').include! + .get_optional_template('some-fallback.ftl').include! + ultimateFallback +) /></programlisting> + + <para>Example, which behaves like <literal><#import 'tags.ftl' as + tags></literal>, except that if <literal>tags.ftl</literal> is + missing, then it creates an empty <literal>tags</literal> hash:</para> + + <programlisting role="template"><#assign tags = (.get_optional_template('tags.ftl').import())!{}></programlisting> + </simplesect> </chapter> <chapter xml:id="ref_reservednames"> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51c24766/src/test/java/freemarker/core/GetOptionalTemplateTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/core/GetOptionalTemplateTest.java b/src/test/java/freemarker/core/GetOptionalTemplateTest.java new file mode 100644 index 0000000..4ceb3e4 --- /dev/null +++ b/src/test/java/freemarker/core/GetOptionalTemplateTest.java @@ -0,0 +1,179 @@ +/* + * 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 java.nio.charset.StandardCharsets; + +import org.junit.Test; + +import freemarker.cache.ByteArrayTemplateLoader; +import freemarker.cache.MultiTemplateLoader; +import freemarker.cache.StringTemplateLoader; +import freemarker.cache.TemplateLoader; +import freemarker.template.Configuration; +import freemarker.test.TemplateTest; + +public class GetOptionalTemplateTest extends TemplateTest { + + private ByteArrayTemplateLoader byteArrayTemplateLoader = new ByteArrayTemplateLoader(); + + @Override + protected Configuration createConfiguration() throws Exception { + Configuration cfg = super.createConfiguration(); + cfg.setTemplateLoader( + new MultiTemplateLoader(new TemplateLoader[] { + new StringTemplateLoader(), byteArrayTemplateLoader + })); + return cfg; + } + + @Test + public void testBasicsWhenTemplateExists() throws Exception { + addTemplate("inc.ftl", "<#assign x = (x!0) + 1>inc ${x}"); + assertOutput("" + + "<#assign t = .getOptionalTemplate('inc.ftl')>" + + "Exists: ${t.exists?c}; " + + "Include: <@t.include />, <@t.include />; " + + "Import: <#assign ns1 = t.import()><#assign ns2 = t.import()>${ns1.x}, ${ns2.x}; " + + "Aliased: <#assign x = 9 in ns1>${ns1.x}, ${ns2.x}, <#import 'inc.ftl' as ns3>${ns3.x}", + "Exists: true; " + + "Include: inc 1, inc 2; " + + "Import: 1, 1; " + + "Aliased: 9, 9, 9" + ); + } + + @Test + public void testBasicsWhenTemplateIsMissing() throws Exception { + assertOutput("" + + "<#assign t = .getOptionalTemplate('missing.ftl')>" + + "Exists: ${t.exists?c}; " + + "Include: ${t.include???c}; " + + "Import: ${t.import???c}", + "Exists: false; " + + "Include: false; " + + "Import: false" + ); + } + + @Test + public void testOptions() throws Exception { + addTemplate("inc.ftl", "${1}"); + assertOutput("" + + "<#assign t = .getOptionalTemplate('inc.ftl', { 'parse': false })>" + + "<@t.include />", + "${1}"); + assertOutput("" + + "<#assign t = .getOptionalTemplate('inc.ftl')>" + + "<@t.include />", + "1"); + assertOutput("" + + "<#assign t = .getOptionalTemplate('inc.ftl', {})>" + + "<@t.include />", + "1"); + + byteArrayTemplateLoader.putTemplate("inc-u16.ftl", "foo".getBytes(StandardCharsets.UTF_16BE)); + assertOutput("" + + "<#assign t = .getOptionalTemplate('inc-u16.ftl', { 'encoding': 'utf-16be' })>" + + "<@t.include />", + "foo"); + assertOutput("" + + "<#assign t = .getOptionalTemplate('inc-u16.ftl')>" + + "<@t.include />", + "\u0000f\u0000o\u0000o"); + + byteArrayTemplateLoader.putTemplate("inc-u16.ftl", "foo${1}".getBytes(StandardCharsets.UTF_16BE)); + assertOutput("" + + "<#assign t = .getOptionalTemplate('inc-u16.ftl', { 'parse': false, 'encoding': 'utf-16be' })>" + + "<@t.include />", + "foo${1}"); + } + + @Test + public void testRelativeAndAbsolutePath() throws Exception { + addTemplate("lib/inc.ftl", "included"); + + addTemplate("test1.ftl", "<#include 'lib/inc.ftl'>"); + assertOutputForNamed("test1.ftl", "included"); + + addTemplate("lib/test2.ftl", "<#include '/lib/inc.ftl'>"); + assertOutputForNamed("lib/test2.ftl", "included"); + + addTemplate("lib/test3.ftl", "<#include 'inc.ftl'>"); + assertOutputForNamed("lib/test3.ftl", "included"); + + addTemplate("sub/test4.ftl", "<#include '../lib/inc.ftl'>"); + assertOutputForNamed("sub/test4.ftl", "included"); + } + + @Test + public void testUseCase1() throws Exception { + addTemplate("lib/inc.ftl", "included"); + assertOutput("" + + "<#macro test templateName>" + + "<#local t = .getOptionalTemplate(templateName)>" + + "<#if t.exists>" + + "before <@t.include /> after" + + "<#else>" + + "missing" + + "</#if>" + + "</#macro>" + + "<@test 'lib/inc.ftl' />; " + + "<@test 'inc.ftl' />", + "before included after; missing"); + } + + @Test + public void testUseCase2() throws Exception { + addTemplate("found.ftl", "found"); + assertOutput("" + + "<@(" + + ".getOptionalTemplate('missing1.ftl').include!" + + ".getOptionalTemplate('missing2.ftl').include!" + + ".getOptionalTemplate('found.ftl').include!" + + ".getOptionalTemplate('missing3.ftl').include" + + ") />", + "found"); + assertOutput("" + + "<#macro fallback>fallback</#macro>" + + "<@(" + + ".getOptionalTemplate('missing1.ftl').include!" + + ".getOptionalTemplate('missing2.ftl').include!" + + "fallback" + + ") />", + "fallback"); + } + + @Test + public void testWrongArguments() throws Exception { + assertErrorContains("<#assign t = .getOptionalTemplate()>", ".getOptionalTemplate", "arguments", "none"); + assertErrorContains("<#assign t = .get_optional_template()>", ".get_optional_template", "arguments", "none"); + assertErrorContains("<#assign t = .getOptionalTemplate(1, 2, 3)>", "arguments", "3"); + assertErrorContains("<#assign t = .getOptionalTemplate(1)>", "#1", "string", "number"); + assertErrorContains("<#assign t = .getOptionalTemplate('x', 1)>", "#2", "hash", "number"); + assertErrorContains("<#assign t = .getOptionalTemplate('x', { 'foo': 1 })>", + "#2", "foo", "encoding", "parse"); + assertErrorContains("<#assign t = .getOptionalTemplate('x', { 'parse': 1 })>", + "#2", "parse", "number", "boolean"); + assertErrorContains("<#assign t = .getOptionalTemplate('x', { 'encoding': 1 })>", + "#2", "encoding", "number", "string"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/51c24766/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java ---------------------------------------------------------------------- diff --git a/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java b/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java index 6179e17..c93a2e5 100644 --- a/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java +++ b/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java @@ -33,35 +33,35 @@ import freemarker.template.DefaultNonListCollectionAdapter; import freemarker.template.DefaultObjectWrapperBuilder; import freemarker.template.TemplateCollectionModel; import freemarker.template.TemplateHashModelEx; +import freemarker.template.TemplateHashModelEx2; import freemarker.template.TemplateHashModelEx2.KeyValuePair; import freemarker.template.TemplateHashModelEx2.KeyValuePairIterator; import freemarker.template.TemplateModel; import freemarker.template.TemplateModelException; import freemarker.template.TemplateNumberModel; import freemarker.template.TemplateScalarModel; -import freemarker.test.TemplateTest; -public class TemplateModelUtilTest extends TemplateTest { +public class TemplateModelUtilTest { @Test public void testGetKeyValuePairIterator() throws Exception { Map<Object, Object> map = new LinkedHashMap<Object, Object>(); TemplateHashModelEx thme = new TemplateHashModelExOnly(map); - assertetKeyValuePairIteratorResult("", thme); + assertetGetKeyValuePairIteratorContent("", thme); map.put("k1", 11); - assertetKeyValuePairIteratorResult("str(k1): num(11)", thme); + assertetGetKeyValuePairIteratorContent("str(k1): num(11)", thme); map.put("k2", "v2"); - assertetKeyValuePairIteratorResult("str(k1): num(11), str(k2): str(v2)", thme); + assertetGetKeyValuePairIteratorContent("str(k1): num(11), str(k2): str(v2)", thme); map.put("k2", null); - assertetKeyValuePairIteratorResult("str(k1): num(11), str(k2): null", thme); + assertetGetKeyValuePairIteratorContent("str(k1): num(11), str(k2): null", thme); map.put(3, 33); try { - assertetKeyValuePairIteratorResult("fails anyway...", thme); + assertetGetKeyValuePairIteratorContent("fails anyway...", thme); fail(); } catch (TemplateModelException e) { assertThat(e.getMessage(), @@ -71,7 +71,7 @@ public class TemplateModelUtilTest extends TemplateTest { map.put(null, 44); try { - assertetKeyValuePairIteratorResult("fails anyway...", thme); + assertetGetKeyValuePairIteratorContent("fails anyway...", thme); fail(); } catch (TemplateModelException e) { assertThat(e.getMessage(), @@ -83,19 +83,20 @@ public class TemplateModelUtilTest extends TemplateTest { public void testGetKeyValuePairIteratorWithEx2() throws Exception { Map<Object, Object> map = new LinkedHashMap<Object, Object>(); TemplateHashModelEx thme = DefaultMapAdapter.adapt( - map, (ObjectWrapperWithAPISupport) getConfiguration().getObjectWrapper()); + map, new DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27).build()); - assertetKeyValuePairIteratorResult("", thme); + assertetGetKeyValuePairIteratorContent("", thme); map.put("k1", 11); map.put("k2", "v2"); map.put("k2", null); map.put(3, 33); map.put(null, 44); - assertetKeyValuePairIteratorResult("str(k1): num(11), str(k2): null, num(3): num(33), null: num(44)", thme); + assertetGetKeyValuePairIteratorContent( + "str(k1): num(11), str(k2): null, num(3): num(33), null: num(44)", thme); } - private void assertetKeyValuePairIteratorResult(String expected, TemplateHashModelEx thme) + private void assertetGetKeyValuePairIteratorContent(String expected, TemplateHashModelEx thme) throws TemplateModelException { StringBuilder sb = new StringBuilder(); KeyValuePairIterator kvpi = TemplateModelUtils.getKeyValuePairIterator(thme); @@ -104,11 +105,12 @@ public class TemplateModelUtilTest extends TemplateTest { if (sb.length() != 0) { sb.append(", "); } - sb.append(toAssertionString(kvp.getKey())).append(": ").append(toAssertionString(kvp.getValue())); + sb.append(toValueAssertionString(kvp.getKey())).append(": ") + .append(toValueAssertionString(kvp.getValue())); } } - private String toAssertionString(TemplateModel model) throws TemplateModelException { + private String toValueAssertionString(TemplateModel model) throws TemplateModelException { if (model instanceof TemplateNumberModel) { return "num(" + ((TemplateNumberModel) model).getAsNumber() + ")"; } else if (model instanceof TemplateScalarModel) { @@ -120,6 +122,9 @@ public class TemplateModelUtilTest extends TemplateTest { throw new IllegalArgumentException("Type unsupported by test: " + model.getClass().getName()); } + /** + * Deliberately doesn't implement {@link TemplateHashModelEx2}, only {@link TemplateHashModelEx}. + */ private static class TemplateHashModelExOnly implements TemplateHashModelEx { private final Map<?, ?> map; @@ -139,7 +144,7 @@ public class TemplateModelUtilTest extends TemplateTest { } public int size() throws TemplateModelException { - return 2; + return map.size(); } public TemplateCollectionModel keys() throws TemplateModelException {