You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@freemarker.apache.org by dd...@apache.org on 2018/02/28 19:49:54 UTC
incubator-freemarker git commit: FREEMARKER-84: Added
.get_optional_template
Repository: incubator-freemarker
Updated Branches:
refs/heads/2.3-gae 59f2e7b8c -> 51c247662
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-gae
Commit: 51c2476621809d8f4183f23e894be0106cabe810
Parents: 59f2e7b
Author: ddekany <dd...@apache.org>
Authored: Wed Feb 28 20:49:36 2018 +0100
Committer: ddekany <dd...@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 {