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");
+ }
+}