Forward ported .getOptionalTemplate from 2.3-gae. Note that we don't have 
"encoding" and "parse" options, as they aren't relevant in FM3.


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/99174103
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/99174103
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/99174103

Branch: refs/heads/3
Commit: 99174103a9a53e7d596deb3fa00aac2420aceefb
Parents: f9fa582
Author: ddekany <ddek...@apache.org>
Authored: Fri Mar 2 13:11:30 2018 +0100
Committer: ddekany <ddek...@apache.org>
Committed: Fri Mar 2 13:11:30 2018 +0100

----------------------------------------------------------------------
 .../core/GetOptionalTemplateTest.java           | 114 ++++++++++++++++++
 .../freemarker/core/ASTExpBuiltInVariable.java  |   5 +
 .../core/GetOptionalTemplateFunction.java       | 117 +++++++++++++++++++
 3 files changed, 236 insertions(+)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99174103/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
new file mode 100644
index 0000000..4149e02
--- /dev/null
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/GetOptionalTemplateTest.java
@@ -0,0 +1,114 @@
+package org.apache.freemarker.core;
+
+import org.apache.freemarker.core.templateresolver.TemplateLoader;
+import 
org.apache.freemarker.core.templateresolver.impl.ByteArrayTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.MultiTemplateLoader;
+import org.apache.freemarker.core.templateresolver.impl.StringTemplateLoader;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+public class GetOptionalTemplateTest extends TemplateTest {
+
+    private ByteArrayTemplateLoader byteArrayTemplateLoader = new 
ByteArrayTemplateLoader();
+    
+    @Override
+    protected Configuration createDefaultConfiguration() throws Exception {
+        return new Configuration.Builder(Configuration.VERSION_3_0_0)
+                .templateLoader(
+                        new MultiTemplateLoader(new TemplateLoader[] {
+                                new StringTemplateLoader(), 
byteArrayTemplateLoader
+                        })).build();
+    }
+
+    @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 testRelativeAndAbsolutePath() throws Exception {
+        addTemplate("lib/inc.ftl", "included");
+        
+        addTemplate("test1.ftl", 
"<@.getOptionalTemplate('lib/inc.ftl').include />");
+        assertOutputForNamed("test1.ftl", "included");
+        
+        addTemplate("lib/test2.ftl", 
"<@.getOptionalTemplate('/lib/inc.ftl').include />");
+        assertOutputForNamed("lib/test2.ftl", "included");
+        
+        addTemplate("lib/test3.ftl", 
"<@.getOptionalTemplate('inc.ftl').include />");
+        assertOutputForNamed("lib/test3.ftl", "included");
+        
+        addTemplate("sub/test4.ftl", 
"<@.getOptionalTemplate('../lib/inc.ftl').include />");
+        assertOutputForNamed("sub/test4.ftl", "included");
+    }
+
+    @Test
+    public void testUseCase1() throws Exception {
+        addTemplate("lib/inc.ftl", "included");
+        assertOutput(""
+                + "<#macro test templateName{positional}>"
+                + "<#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()>", 
"argument");
+        assertErrorContains("<#assign t = .getOptionalTemplate('1', '2', 
'3')>", "arguments", "3");
+        assertErrorContains("<#assign t = .getOptionalTemplate(1)>", "1st 
argument", "string", "number");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99174103/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
index 19b0b04..b9d2f12 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
@@ -57,6 +57,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
     static final String AUTO_ESC = "autoEsc";
     static final String URL_ESCAPING_CHARSET = "urlEscapingCharset";
     static final String NOW = "now";
+    static final String GET_OPTIONAL_TEMPLATE = "getOptionalTemplate";
     
     static final Set<String> BUILT_IN_VARIABLE_NAMES = new _SortedArraySet<>(
         // Must be sorted alphabetically!
@@ -64,6 +65,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
         CURRENT_TEMPLATE_NAME,
         DATA_MODEL,
         ERROR,
+        GET_OPTIONAL_TEMPLATE,
         GLOBALS,
         INCOMPATIBLE_IMPROVEMENTS,
         LANG,
@@ -194,6 +196,9 @@ final class ASTExpBuiltInVariable extends ASTExpression {
         if (name == INCOMPATIBLE_IMPROVEMENTS) {
             return new 
SimpleString(env.getConfiguration().getIncompatibleImprovements().toString());
         }
+        if (name == GET_OPTIONAL_TEMPLATE) {
+            return GetOptionalTemplateFunction.INSTANCE;
+        }        
         
         throw new TemplateException(this,
                 "Invalid special variable: ", name);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/99174103/freemarker-core/src/main/java/org/apache/freemarker/core/GetOptionalTemplateFunction.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/GetOptionalTemplateFunction.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/GetOptionalTemplateFunction.java
new file mode 100644
index 0000000..eb8b6da
--- /dev/null
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/GetOptionalTemplateFunction.java
@@ -0,0 +1,117 @@
+/*
+ * 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.core.util.CallableUtils.*;
+
+import java.io.IOException;
+import java.io.Writer;
+
+import org.apache.freemarker.core.model.ArgumentArrayLayout;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateDirectiveModel;
+import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import 
org.apache.freemarker.core.templateresolver.MalformedTemplateNameException;
+
+/**
+ * Implements {@code .getOptionalTemplate(name, options)}.
+ */
+class GetOptionalTemplateFunction implements TemplateFunctionModel {
+
+    static final GetOptionalTemplateFunction INSTANCE = new 
GetOptionalTemplateFunction();
+    
+    private static final String RESULT_INCLUDE = "include";
+    private static final String RESULT_IMPORT = "import";
+    private static final String RESULT_EXISTS = "exists";
+
+    private GetOptionalTemplateFunction() {
+        // No op.
+    }
+
+    @Override
+    public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, 
Environment env) throws TemplateException {
+        final String absTemplateName;
+        try {
+            absTemplateName = env.toFullTemplateName(
+                    env.getCurrentTemplate().getLookupName(),
+                    getStringArgument(args, 0, this));
+        } catch (MalformedTemplateNameException e) {
+            throw new TemplateException("Failed to convert template path to 
full path; see cause exception.", e);
+        }
+
+        final Template template;
+        try {
+            template = env.getTemplateForInclusion(absTemplateName, true);
+        } catch (IOException e) {
+            throw new TemplateException(
+                    "Error when trying to include template ", new 
_DelayedJQuote(absTemplateName));
+        }
+        
+        NativeHashEx result = new NativeHashEx();
+        result.put(RESULT_EXISTS, template != null ? TemplateBooleanModel.TRUE 
: TemplateBooleanModel.FALSE);
+        // If the template is missing, result.include and such will be missing 
too, so that a default can be
+        // conveniently provided like in <@optTemp.include!myDefaultMacro />.
+        if (template != null) {
+            result.put(RESULT_INCLUDE, new TemplateDirectiveModel() {
+                @Override
+                public void execute(TemplateModel[] args, CallPlace callPlace, 
Writer out, Environment env)
+                        throws TemplateException, IOException {
+                    env.include(template);
+                }
+
+                @Override
+                public boolean isNestedContentSupported() {
+                    return false;
+                }
+
+                @Override
+                public ArgumentArrayLayout getDirectiveArgumentArrayLayout() {
+                    return ArgumentArrayLayout.PARAMETERLESS;
+                }
+            });
+            result.put(RESULT_IMPORT, new TemplateFunctionModel() {
+                @Override
+                public TemplateModel execute(TemplateModel[] args, CallPlace 
callPlace, Environment env)
+                        throws TemplateException {
+                    try {
+                        return env.importLib(template, null);
+                    } catch (IOException e) {
+                        throw new TemplateException(e, "Failed to import 
loaded template; see cause exception");
+                    } catch (TemplateException e) {
+                        throw new TemplateException(e, "Failed to import 
loaded template; see cause exception");
+                    }
+                }
+
+                @Override
+                public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+                    return ArgumentArrayLayout.PARAMETERLESS;
+                }
+            });
+        }
+        return result;
+    }
+
+    @Override
+    public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
+        return ArgumentArrayLayout.SINGLE_POSITIONAL_PARAMETER;
+    }
+
+}

Reply via email to