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 2019/06/29 21:43:08 UTC

[freemarker] branch 2.3-gae updated: Don't fall back to higher scope if the argument in a lambda expression is null. This allows reliably filtering/mapping the elements of a collection that contains some null-s.

This is an automated email from the ASF dual-hosted git repository.

ddekany pushed a commit to branch 2.3-gae
in repository https://gitbox.apache.org/repos/asf/freemarker.git


The following commit(s) were added to refs/heads/2.3-gae by this push:
     new 364da18  Don't fall back to higher scope if the argument in a lambda expression is null. This allows reliably filtering/mapping the elements of a collection that contains some null-s.
364da18 is described below

commit 364da18c567544251f4f30c323fc8e8f273f674f
Author: ddekany <dd...@apache.org>
AuthorDate: Sat Jun 29 23:42:25 2019 +0200

    Don't fall back to higher scope if the argument in a lambda expression is null. This allows reliably filtering/mapping the elements of a collection that contains some null-s.
---
 src/main/java/freemarker/core/Environment.java     | 26 ++++++++++---
 .../freemarker/core/LocalLambdaExpression.java     |  3 +-
 .../java/freemarker/core/NullTemplateModel.java    | 39 +++++++++++++++++++
 .../java/freemarker/core/NullTransparencyTest.java | 45 ++++++++++++++++++++++
 4 files changed, 106 insertions(+), 7 deletions(-)

diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 2bacc33..d1ebace 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -2038,6 +2038,17 @@ public final class Environment extends Configurable {
      * terminology.)
      */
     public TemplateModel getLocalVariable(String name) throws TemplateModelException {
+        TemplateModel val = getNullableLocalVariable(name);
+        return val != NullTemplateModel.INSTANCE ? val : null;
+    }
+
+    /**
+     * Similar to {@link #getLocalVariable(String)}, but might returns {@link NullTemplateModel}. Only used internally,
+     * as {@link NullTemplateModel} is internal.
+     *
+     * @since 2.3.29
+     */
+    final TemplateModel getNullableLocalVariable(String name) throws TemplateModelException {
         if (localContextStack != null) {
             for (int i = localContextStack.size() - 1; i >= 0; i--) {
                 LocalContext lc = localContextStack.get(i);
@@ -2068,14 +2079,17 @@ public final class Environment extends Configurable {
      * </ol>
      */
     public TemplateModel getVariable(String name) throws TemplateModelException {
-        TemplateModel result = getLocalVariable(name);
-        if (result == null) {
-            result = currentNamespace.get(name);
+        TemplateModel result = getNullableLocalVariable(name);
+        if (result != null) {
+            return result != NullTemplateModel.INSTANCE ? result : null;
         }
-        if (result == null) {
-            result = getGlobalVariable(name);
+
+        result = currentNamespace.get(name);
+        if (result != null) {
+            return result;
+
         }
-        return result;
+        return getGlobalVariable(name);
     }
 
     /**
diff --git a/src/main/java/freemarker/core/LocalLambdaExpression.java b/src/main/java/freemarker/core/LocalLambdaExpression.java
index 537efb1..b7d1896 100644
--- a/src/main/java/freemarker/core/LocalLambdaExpression.java
+++ b/src/main/java/freemarker/core/LocalLambdaExpression.java
@@ -62,7 +62,8 @@ final class LocalLambdaExpression extends Expression {
      * Call the function defined by the lambda expression; overload specialized for 1 argument, the most common case.
      */
     TemplateModel invokeLambdaDefinedFunction(TemplateModel argValue, Environment env) throws TemplateException {
-        return env.evaluateWithNewLocal(rho, lho.getParameters().get(0).getName(), argValue);
+        return env.evaluateWithNewLocal(rho, lho.getParameters().get(0).getName(),
+                argValue != null ? argValue : NullTemplateModel.INSTANCE);
     }
 
     @Override
diff --git a/src/main/java/freemarker/core/NullTemplateModel.java b/src/main/java/freemarker/core/NullTemplateModel.java
new file mode 100644
index 0000000..f0f0a22
--- /dev/null
+++ b/src/main/java/freemarker/core/NullTemplateModel.java
@@ -0,0 +1,39 @@
+/*
+ * 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 freemarker.template.TemplateModel;
+
+/**
+ * Represents a {@code null} value; if we get this as the value of a variable from a scope, we do not fall back
+ * to a higher scope to get the same variable again. If instead we get a {@code null}, that means that the variable
+ * doesn't exist at all in the current scope, and so we fall back to a higher scope. This distinction wasn is only
+ * used for (and expected from) certain scopes, so be careful where you are using it. (As of this
+ * writing, it's onlt for local variables, including loop variables). The user should never meet a
+ * {@link NullTemplateModel}, it must not be returned from public API-s.
+ *
+ * @see Environment#getNullableLocalVariable(String)
+ *
+ * @since 2.3.29
+ */
+final class NullTemplateModel implements TemplateModel {
+    static final NullTemplateModel INSTANCE = new NullTemplateModel();
+    private NullTemplateModel() { }
+}
diff --git a/src/test/java/freemarker/core/NullTransparencyTest.java b/src/test/java/freemarker/core/NullTransparencyTest.java
new file mode 100644
index 0000000..2f99d0b
--- /dev/null
+++ b/src/test/java/freemarker/core/NullTransparencyTest.java
@@ -0,0 +1,45 @@
+/*
+ * 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.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+
+import freemarker.test.TemplateTest;
+
+public class NullTransparencyTest extends TemplateTest {
+
+    @Test
+    public void test() throws Exception {
+        List<String> xs = new ArrayList<String>();
+        xs.add("a");
+        xs.add(null);
+        xs.add("b");
+        addToDataModel("xs", xs);
+        assertOutput("<#list xs?filter(it -> it??) as it>${it!'null'}<#sep>, </#list>", "a, b");
+        assertOutput("<#list xs?map(it -> it!'null') as it>${it}<#sep>, </#list>", "a, null, b");
+
+        // Lambdas are always use non-transparent nulls, as there was no backwrad compatibility constraint:
+        assertOutput("<#assign it='fallback'><#list xs?map(it -> it!'null') as it>${it}<#sep>, </#list>",
+                "a, null, b");
+    }
+}