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/08/01 19:56:32 UTC

[freemarker] branch 2.3-gae updated (2e98593 -> 522747b)

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

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


    from 2e98593  (Fixed import code style inconsistencies.)
     new 44e30e0  (Moved IntermediateStreamOperationLikeBuiltIn out of BuiltInsForSequences, as it should be usable for FTL hash targets as well.)
     new 1053eef  Added ?take_while(predicate) and ?drop_while(predicate).
     new 522747b  Added missing change log entry for the new fallback_on_null_loop_variable configuration setting.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/main/java/freemarker/core/BuiltIn.java         |   6 +-
 .../java/freemarker/core/BuiltInsForSequences.java | 376 ++++++++++-----------
 .../IntermediateStreamOperationLikeBuiltIn.java    | 221 ++++++++++++
 src/main/java/freemarker/core/_MessageUtil.java    |   6 +
 src/manual/en_US/book.xml                          |  29 +-
 .../java/freemarker/core/NullTransparencyTest.java |   3 +-
 .../core/TakeWhileAndDropWhileBiTest.java          | 148 ++++++++
 7 files changed, 579 insertions(+), 210 deletions(-)
 create mode 100644 src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java
 create mode 100644 src/test/java/freemarker/core/TakeWhileAndDropWhileBiTest.java


[freemarker] 03/03: Added missing change log entry for the new fallback_on_null_loop_variable configuration setting.

Posted by dd...@apache.org.
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

commit 522747bec7d37fbdcd860a501f116a1a363e1608
Author: ddekany <dd...@apache.org>
AuthorDate: Thu Aug 1 21:49:07 2019 +0200

    Added missing change log entry for the new fallback_on_null_loop_variable configuration setting.
---
 src/manual/en_US/book.xml | 17 +++++++++++++++++
 1 file changed, 17 insertions(+)

diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index ec49d8e..017e7c0 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -28006,6 +28006,23 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
             </listitem>
 
             <listitem>
+              <para>New configuration setting,
+              <literal>fallback_on_null_loop_variable</literal>: Specifies the
+              behavior when reading a loop variable (like <literal>i</literal>
+              in <literal>&lt;#list items as i&gt;</literal>, or in
+              <literal>&lt;@myMacro items; i&gt;</literal>) that's
+              <literal>null</literal> (missing); if <literal>true</literal>,
+              FreeMarker will look for a variable with the same name in higher
+              variable scopes, or if <literal>false</literal> the variable
+              will be simply <literal>null</literal> (missing). For backward
+              compatibility the default is <literal>true</literal>. The
+              recommended value for new projects is <literal>false</literal>,
+              as otherwise adding new variables to higher scopes (typically to
+              the data-model) can unintentionally change the behavior of
+              templates.</para>
+            </listitem>
+
+            <listitem>
               <para>If the result of
               <literal><replaceable>seq</replaceable>?size</literal> is
               compared to an integer literal in a template, like in


[freemarker] 02/03: Added ?take_while(predicate) and ?drop_while(predicate).

Posted by dd...@apache.org.
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

commit 1053eef00b76182beebc62e1a9f289f8e2be1326
Author: ddekany <dd...@apache.org>
AuthorDate: Thu Aug 1 21:28:45 2019 +0200

    Added ?take_while(predicate) and ?drop_while(predicate).
---
 src/main/java/freemarker/core/BuiltIn.java         |   4 +-
 .../java/freemarker/core/BuiltInsForSequences.java | 195 +++++++++++++++++++--
 src/main/java/freemarker/core/_MessageUtil.java    |   6 +
 src/manual/en_US/book.xml                          |  12 +-
 .../java/freemarker/core/NullTransparencyTest.java |   3 +-
 .../core/TakeWhileAndDropWhileBiTest.java          | 148 ++++++++++++++++
 6 files changed, 345 insertions(+), 23 deletions(-)

diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index 5322eed..5e143b1 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -84,7 +84,7 @@ abstract class BuiltIn extends Expression implements Cloneable {
 
     static final Set<String> CAMEL_CASE_NAMES = new TreeSet<String>();
     static final Set<String> SNAKE_CASE_NAMES = new TreeSet<String>();
-    static final int NUMBER_OF_BIS = 281;
+    static final int NUMBER_OF_BIS = 285;
     static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
 
     static {
@@ -109,6 +109,7 @@ abstract class BuiltIn extends Expression implements Cloneable {
         putBI("datetime_if_unknown", "datetimeIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.DATETIME));
         putBI("default", new BuiltInsForExistenceHandling.defaultBI());
         putBI("double", new doubleBI());
+        putBI("drop_while", "dropWhile", new BuiltInsForSequences.drop_whileBI());
         putBI("ends_with", "endsWith", new BuiltInsForStringsBasic.ends_withBI());
         putBI("ensure_ends_with", "ensureEndsWith", new BuiltInsForStringsBasic.ensure_ends_withBI());
         putBI("ensure_starts_with", "ensureStartsWith", new BuiltInsForStringsBasic.ensure_starts_withBI());
@@ -278,6 +279,7 @@ abstract class BuiltIn extends Expression implements Cloneable {
         putBI("starts_with", "startsWith", new BuiltInsForStringsBasic.starts_withBI());
         putBI("string", new BuiltInsForMultipleTypes.stringBI());
         putBI("substring", new BuiltInsForStringsBasic.substringBI());
+        putBI("take_while", "takeWhile", new BuiltInsForSequences.take_whileBI());
         putBI("then", new BuiltInsWithLazyConditionals.then_BI());
         putBI("time", new BuiltInsForMultipleTypes.dateBI(TemplateDateModel.TIME));
         putBI("time_if_unknown", "timeIfUnknown", new BuiltInsForDates.dateType_if_unknownBI(TemplateDateModel.TIME));
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index 7c8afc5..153be79 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -1000,7 +1000,27 @@ class BuiltInsForSequences {
         
     }
 
-    static class filterBI extends IntermediateStreamOperationLikeBuiltIn {
+    private static abstract class FilterLikeBI extends IntermediateStreamOperationLikeBuiltIn {
+        protected final boolean elementMatches(TemplateModel element, ElementTransformer elementTransformer,
+                Environment env)
+                throws TemplateException {
+            TemplateModel transformedElement = elementTransformer.transformElement(element, env);
+            if (!(transformedElement instanceof TemplateBooleanModel)) {
+                if (transformedElement == null) {
+                    throw new _TemplateModelException(getElementTransformerExp(), env,
+                            "The filter expression has returned no value (has returned null), " +
+                                    "rather than a boolean.");
+                }
+                throw new _TemplateModelException(getElementTransformerExp(), env,
+                        "The filter expression had to return a boolean value, but it returned ",
+                        new _DelayedAOrAn(new _DelayedFTLTypeDescription(transformedElement)),
+                        " instead.");
+            }
+            return ((TemplateBooleanModel) transformedElement).getAsBoolean();
+        }
+    }
+
+    static class filterBI extends FilterLikeBI {
 
         protected TemplateModel calculateResult(
                 final TemplateModelIterator lhoIterator, final TemplateModel lho,
@@ -1073,21 +1093,79 @@ class BuiltInsForSequences {
             }
         }
 
-        private boolean elementMatches(TemplateModel element, ElementTransformer elementTransformer, Environment env)
-                throws TemplateException {
-            TemplateModel transformedElement = elementTransformer.transformElement(element, env);
-            if (!(transformedElement instanceof TemplateBooleanModel)) {
-                if (transformedElement == null) {
-                    throw new _TemplateModelException(getElementTransformerExp(), env,
-                            "The filter expression has returned no value (has returned null), " +
-                            "rather than a boolean.");
+    }
+
+    static class take_whileBI extends FilterLikeBI {
+
+        protected TemplateModel calculateResult(
+                final TemplateModelIterator lhoIterator, final TemplateModel lho,
+                boolean lhoIsSequence, final ElementTransformer elementTransformer,
+                final Environment env) throws TemplateException {
+            if (!isLazilyGeneratedResultEnabled()) {
+                if (!lhoIsSequence) {
+                    throw _MessageUtil.newLazilyGeneratedCollectionMustBeSequenceException(take_whileBI.this);
                 }
-                throw new _TemplateModelException(getElementTransformerExp(), env,
-                        "The filter expression had to return a boolean value, but it returned ",
-                        new _DelayedAOrAn(new _DelayedFTLTypeDescription(transformedElement)),
-                        " instead.");
+
+                List<TemplateModel> resultList = new ArrayList<TemplateModel>();
+                while (lhoIterator.hasNext()) {
+                    TemplateModel element = lhoIterator.next();
+                    if (elementMatches(element, elementTransformer, env)) {
+                        resultList.add(element);
+                    } else {
+                        break;
+                    }
+                }
+                return new TemplateModelListSequence(resultList);
+            } else {
+                return new LazilyGeneratedCollectionModelWithUnknownSize(
+                        new TemplateModelIterator() {
+                            boolean prefetchDone;
+                            TemplateModel prefetchedElement;
+                            boolean prefetchedEndOfIterator;
+
+                            public TemplateModel next() throws TemplateModelException {
+                                ensurePrefetchDone();
+                                if (prefetchedEndOfIterator) {
+                                    throw new IllegalStateException("next() was called when hasNext() is false");
+                                }
+                                prefetchDone = false;
+                                return prefetchedElement;
+                            }
+
+                            public boolean hasNext() throws TemplateModelException {
+                                ensurePrefetchDone();
+                                return !prefetchedEndOfIterator;
+                            }
+
+                            private void ensurePrefetchDone() throws TemplateModelException {
+                                if (prefetchDone) {
+                                    return;
+                                }
+
+                                if (lhoIterator.hasNext()) {
+                                    TemplateModel element = lhoIterator.next();
+                                    boolean elementMatched;
+                                    try {
+                                        elementMatched = elementMatches(element, elementTransformer, env);
+                                    } catch (TemplateException e) {
+                                        throw new _TemplateModelException(e, env, "Failed to transform element");
+                                    }
+                                    if (elementMatched) {
+                                        prefetchedElement = element;
+                                    } else {
+                                        prefetchedEndOfIterator = true;
+                                        prefetchedElement = null;
+                                    }
+                                } else {
+                                    prefetchedEndOfIterator = true;
+                                    prefetchedElement = null;
+                                }
+                                prefetchDone = true;
+                            }
+                        },
+                        lhoIsSequence
+                );
             }
-            return ((TemplateBooleanModel) transformedElement).getAsBoolean();
         }
 
     }
@@ -1147,6 +1225,95 @@ class BuiltInsForSequences {
 
     }
 
+    static class drop_whileBI extends FilterLikeBI {
+
+        protected TemplateModel calculateResult(
+                final TemplateModelIterator lhoIterator, final TemplateModel lho,
+                boolean lhoIsSequence, final ElementTransformer elementTransformer,
+                final Environment env) throws TemplateException {
+            if (!isLazilyGeneratedResultEnabled()) {
+                if (!lhoIsSequence) {
+                    throw _MessageUtil.newLazilyGeneratedCollectionMustBeSequenceException(drop_whileBI.this);
+                }
+
+                List<TemplateModel> resultList = new ArrayList<TemplateModel>();
+                while (lhoIterator.hasNext()) {
+                    TemplateModel element = lhoIterator.next();
+                    if (!elementMatches(element, elementTransformer, env)) {
+                        resultList.add(element);
+                        while (lhoIterator.hasNext()) {
+                            resultList.add(lhoIterator.next());
+                        }
+                        break;
+                    }
+                }
+                return new TemplateModelListSequence(resultList);
+            } else {
+                return new LazilyGeneratedCollectionModelWithUnknownSize(
+                        new TemplateModelIterator() {
+                            boolean dropMode = true;
+                            boolean prefetchDone;
+                            TemplateModel prefetchedElement;
+                            boolean prefetchedEndOfIterator;
+
+                            public TemplateModel next() throws TemplateModelException {
+                                ensurePrefetchDone();
+                                if (prefetchedEndOfIterator) {
+                                    throw new IllegalStateException("next() was called when hasNext() is false");
+                                }
+                                prefetchDone = false;
+                                return prefetchedElement;
+                            }
+
+                            public boolean hasNext() throws TemplateModelException {
+                                ensurePrefetchDone();
+                                return !prefetchedEndOfIterator;
+                            }
+
+                            private void ensurePrefetchDone() throws TemplateModelException {
+                                if (prefetchDone) {
+                                    return;
+                                }
+
+                                if (dropMode) {
+                                    boolean foundElement = false;
+                                    dropElements: while  (lhoIterator.hasNext()) {
+                                        TemplateModel element = lhoIterator.next();
+                                        try {
+                                            if (!elementMatches(element, elementTransformer, env)) {
+                                                prefetchedElement = element;
+                                                foundElement = true;
+                                                break dropElements;
+                                            }
+                                        } catch (TemplateException e) {
+                                            throw new _TemplateModelException(e, env,
+                                                    "Failed to transform element");
+                                        }
+                                    }
+                                    dropMode = false;
+                                    if (!foundElement) {
+                                        prefetchedEndOfIterator = true;
+                                        prefetchedElement = null;
+                                    }
+                                } else {
+                                    if (lhoIterator.hasNext()) {
+                                        TemplateModel element = lhoIterator.next();
+                                        prefetchedElement = element;
+                                    } else {
+                                        prefetchedEndOfIterator = true;
+                                        prefetchedElement = null;
+                                    }
+                                }
+                                prefetchDone = true;
+                            }
+                        },
+                        lhoIsSequence
+                );
+            }
+        }
+
+    }
+
     // Can't be instantiated
     private BuiltInsForSequences() { }
 
diff --git a/src/main/java/freemarker/core/_MessageUtil.java b/src/main/java/freemarker/core/_MessageUtil.java
index 4d2df6a..ebbac10 100644
--- a/src/main/java/freemarker/core/_MessageUtil.java
+++ b/src/main/java/freemarker/core/_MessageUtil.java
@@ -342,6 +342,12 @@ public class _MessageUtil {
                         ", which leads to this restriction."));
     }
 
+    /**
+     * Because of the limitations of FTL lambdas (called "local lambdas"), sometimes we must condense the lazy result
+     * down into a sequence. However, doing that automatically is only allowed if the input was a sequence as well. If
+     * it wasn't a sequence, we don't dare to collect the result into a sequence automatically (because it's possibly
+     * too long), and that's when this error message comes.
+     */
     public static TemplateException newLazilyGeneratedCollectionMustBeSequenceException(Expression blamed) {
         return new _MiscTemplateException(blamed,
                 "The result is a listable value with lazy transformation(s) applied on it, but it's not " +
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 931f768..ec49d8e 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -20,11 +20,7 @@
 <book conformance="docgen" version="5.0" xml:lang="en"
       xmlns="http://docbook.org/ns/docbook"
       xmlns:xlink="http://www.w3.org/1999/xlink"
-      xmlns:xi="http://www.w3.org/2001/XInclude"
-      xmlns:ns5="http://www.w3.org/1999/xhtml"
-      xmlns:ns4="http://www.w3.org/2000/svg"
-      xmlns:ns3="http://www.w3.org/1998/Math/MathML"
-      xmlns:ns="http://docbook.org/ns/docbook">
+>
   <info>
     <title>Apache FreeMarker Manual</title>
 
@@ -27900,8 +27896,10 @@ TemplateModel x = env.getVariable("x");  // get variable x</programlisting>
           <itemizedlist>
             <listitem>
               <para>Added new built-ins:
-              <literal>?filter(<replaceable>predicate</replaceable>)</literal>
-              and <literal>?map(<replaceable>mapper</replaceable>)</literal>.
+              <literal>?filter(<replaceable>predicate</replaceable>)</literal>,
+              <literal>?map(<replaceable>mapper</replaceable>)</literal>,
+              <literal>?take_while(<replaceable>predicate</replaceable>)</literal>,
+              <literal>?drop_while(<replaceable>predicate</replaceable>)</literal>.
               These allow using lambda expression, like
               <literal>users?filter(user -&gt; user.superuser)</literal> or
               <literal>users?map(user -&gt; user.name)</literal>, or accept a
diff --git a/src/test/java/freemarker/core/NullTransparencyTest.java b/src/test/java/freemarker/core/NullTransparencyTest.java
index bde9957..a5f6638 100644
--- a/src/test/java/freemarker/core/NullTransparencyTest.java
+++ b/src/test/java/freemarker/core/NullTransparencyTest.java
@@ -56,7 +56,6 @@ public class NullTransparencyTest extends TemplateTest {
 
     @Test
     public void testWithoutClashingHigherScopeVar() throws Exception {
-
         assertTrue(getConfiguration().getFallbackOnNullLoopVariable());
         testLambdaArguments();
         testLoopVariables("null");
@@ -83,6 +82,8 @@ public class NullTransparencyTest extends TemplateTest {
     protected void testLambdaArguments() throws IOException, TemplateException {
         assertOutput("<#list list?filter(it -> it??) as it>${it!'null'}<#sep>, </#list>",
                 "a, b");
+        assertOutput("<#list list?takeWhile(it -> it??) as it>${it!'null'}<#sep>, </#list>",
+                "a");
         assertOutput("<#list list?map(it -> it!'null') as it>${it}<#sep>, </#list>",
                 "a, null, b");
     }
diff --git a/src/test/java/freemarker/core/TakeWhileAndDropWhileBiTest.java b/src/test/java/freemarker/core/TakeWhileAndDropWhileBiTest.java
new file mode 100644
index 0000000..b55e85d
--- /dev/null
+++ b/src/test/java/freemarker/core/TakeWhileAndDropWhileBiTest.java
@@ -0,0 +1,148 @@
+/*
+ * 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.Collections;
+import java.util.List;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultObjectWrapper;
+import freemarker.test.TemplateTest;
+
+public class TakeWhileAndDropWhileBiTest extends TemplateTest {
+
+    private static class TestParam {
+        private final List<?> list;
+        private final String takeWhileResult;
+        private final String dropWhileResult;
+
+        public TestParam(List<?> list, String takeWhileResult, String dropWhileResult) {
+            this.list = list;
+            this.takeWhileResult = takeWhileResult;
+            this.dropWhileResult = dropWhileResult;
+        }
+    }
+
+    @Override
+    protected Configuration createConfiguration() throws Exception {
+        Configuration cfg = super.createConfiguration();
+
+        DefaultObjectWrapper objectWrapper = new DefaultObjectWrapper(Configuration.VERSION_2_3_28);
+        objectWrapper.setForceLegacyNonListCollections(false);
+        cfg.setObjectWrapper(objectWrapper);
+
+        return cfg;
+    }
+
+    private static final List<TestParam> TEST_PARAMS = ImmutableList.of(
+            new TestParam(ImmutableList.of(),
+                    "",
+                    ""),
+            new TestParam(ImmutableList.of("a"),
+                    "a",
+                    "a"),
+            new TestParam(ImmutableList.of("a", "b", "c"),
+                    "a, b, c",
+                    "a, b, c"),
+            new TestParam(ImmutableList.of("aX"),
+                    "",
+                    ""),
+            new TestParam(ImmutableList.of("aX", "b"),
+                    "",
+                    "b"),
+            new TestParam(ImmutableList.of("aX", "b", "c"),
+                    "",
+                    "b, c"),
+            new TestParam(ImmutableList.of("a", "bX", "c"),
+                    "a",
+                    "a, bX, c"),
+            new TestParam(ImmutableList.of("a", "b", "cX"),
+                    "a, b",
+                    "a, b, cX"),
+            new TestParam(ImmutableList.of("aX", "bX", "c"),
+                    "",
+                    "c"),
+            new TestParam(ImmutableList.of("aX", "bX", "cX"),
+                    "",
+                    ""),
+            new TestParam(ImmutableList.of("aX", "b", "cX"),
+                    "",
+                    "b, cX")
+    );
+
+    @Test
+    public void testTakeWhile() throws Exception {
+        for (TestParam testParam : TEST_PARAMS) {
+            addToDataModel("xs", testParam.list);
+            assertOutput(
+                    "<#list xs?takeWhile(it -> !it?contains('X')) as x>${x}<#sep>, </#list>",
+                    testParam.takeWhileResult);
+            assertOutput(
+                    "<#assign fxs = xs?takeWhile(it -> !it?contains('X'))>" +
+                            "${fxs?join(', ')}",
+                    testParam.takeWhileResult);
+        }
+    }
+
+    @Test
+    public void testDropWhile() throws Exception {
+        for (TestParam testParam : TEST_PARAMS) {
+            addToDataModel("xs", testParam.list);
+            assertOutput(
+                    "<#list xs?dropWhile(it -> it?contains('X')) as x>${x}<#sep>, </#list>",
+                    testParam.dropWhileResult);
+            assertOutput(
+                    "<#assign fxs = xs?dropWhile(it -> it?contains('X'))>" +
+                            "${fxs?join(', ')}",
+                    testParam.dropWhileResult);
+        }
+    }
+
+    // Chaining the two built-ins is not a special case, but, in the hope of running into some bugs, we test that too.
+    @Test
+    public void testBetween() throws Exception {
+        String ftl = "<#list xs?dropWhile(it -> it < 0)?takeWhile(it -> it >= 0) as x>${x}<#sep>, </#list>";
+
+        addToDataModel("xs", ImmutableList.of(-1, -2, 3, 4, -5, -6));
+        assertOutput(ftl,  "3, 4");
+
+        addToDataModel("xs", ImmutableList.of(-1, -2, -5, -6));
+        assertOutput(ftl,  "");
+
+        addToDataModel("xs", ImmutableList.of(1, 2, 3));
+        assertOutput(ftl,  "1, 2, 3");
+
+        addToDataModel("xs", Collections.emptyList());
+        assertOutput(ftl,  "");
+    }
+
+    @Test
+    public void testSnakeCaseNames() throws Exception {
+        addToDataModel("xs", ImmutableList.of(-1, -2, 3, 4, -5, -6));
+        assertOutput(
+                "<#list xs?drop_while(it -> it < 0)?take_while(it -> it >= 0) as x>${x}<#sep>, </#list>",
+                "3, 4");
+    }
+
+}
\ No newline at end of file


[freemarker] 01/03: (Moved IntermediateStreamOperationLikeBuiltIn out of BuiltInsForSequences, as it should be usable for FTL hash targets as well.)

Posted by dd...@apache.org.
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

commit 44e30e0ae281d400e9e4975884b15d751b464256
Author: ddekany <dd...@apache.org>
AuthorDate: Sat Jul 20 17:38:23 2019 +0200

    (Moved IntermediateStreamOperationLikeBuiltIn out of BuiltInsForSequences, as it should be usable for FTL hash targets as well.)
---
 src/main/java/freemarker/core/BuiltIn.java         |   2 +-
 .../java/freemarker/core/BuiltInsForSequences.java | 191 ------------------
 .../IntermediateStreamOperationLikeBuiltIn.java    | 221 +++++++++++++++++++++
 3 files changed, 222 insertions(+), 192 deletions(-)

diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index 4e26f50..5322eed 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -397,7 +397,7 @@ abstract class BuiltIn extends Expression implements Cloneable {
      * If the built-in supports a lazily generated value as its left operand (the target).
      * Don't confuse this with what's allowed for result of the built-in itself; that's influenced by
      * {@link Expression#enableLazilyGeneratedResult()} (and so
-     * {@link BuiltInsForSequences.IntermediateStreamOperationLikeBuiltIn#isLazilyGeneratedTargetResultSupported()}).
+     * {@link IntermediateStreamOperationLikeBuiltIn#isLazilyGeneratedTargetResultSupported()}).
      */
     protected boolean isLazilyGeneratedTargetResultSupported() {
         return false;
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index b0cc82e..7c8afc5 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -37,7 +37,6 @@ import freemarker.template.TemplateCollectionModelEx;
 import freemarker.template.TemplateDateModel;
 import freemarker.template.TemplateException;
 import freemarker.template.TemplateHashModel;
-import freemarker.template.TemplateMethodModel;
 import freemarker.template.TemplateMethodModelEx;
 import freemarker.template.TemplateModel;
 import freemarker.template.TemplateModelException;
@@ -1001,196 +1000,6 @@ class BuiltInsForSequences {
         
     }
 
-    /**
-     * Built-in that's similar to a Java 8 Stream intermediate operation. To be on the safe side, by default these
-     * are eager, and just produce a {@link TemplateSequenceModel}. But when circumstances allow, they become lazy,
-     * similarly to Java 8 Stream intermediate operations. Another characteristic of these built-ins is that they
-     * usually accept lambda expressions as parameters.
-     */
-    static abstract class IntermediateStreamOperationLikeBuiltIn extends BuiltInWithParseTimeParameters {
-
-        private Expression elementTransformerExp;
-        private ElementTransformer precreatedElementTransformer;
-        private boolean lazilyGeneratedResultEnabled;
-
-        @Override
-        void bindToParameters(List<Expression> parameters, Token openParen, Token closeParen) throws ParseException {
-            // At the moment all built-ins of this kind requires 1 parameter.
-            if (parameters.size() != 1) {
-                throw newArgumentCountException("requires exactly 1", openParen, closeParen);
-            }
-            this.elementTransformerExp = parameters.get(0);
-            if (elementTransformerExp instanceof LocalLambdaExpression) {
-                LocalLambdaExpression localLambdaExp = (LocalLambdaExpression) elementTransformerExp;
-                checkLocalLambdaParamCount(localLambdaExp, 1);
-                // We can't do this with other kind of expressions, like a function or method reference, as they
-                // need to be evaluated on runtime:
-                precreatedElementTransformer = new LocalLambdaElementTransformer(localLambdaExp);
-            }
-        }
-
-        @Override
-        protected final boolean isLocalLambdaParameterSupported() {
-            return true;
-        }
-
-        @Override
-        final void enableLazilyGeneratedResult() {
-            this.lazilyGeneratedResultEnabled = true;
-        }
-
-        /** Tells if {@link #enableLazilyGeneratedResult()} was called. */
-        protected final boolean isLazilyGeneratedResultEnabled() {
-            return lazilyGeneratedResultEnabled;
-        }
-
-        @Override
-        protected final boolean isLazilyGeneratedTargetResultSupported() {
-            return true;
-        }
-
-        protected List<Expression> getArgumentsAsList() {
-            return Collections.singletonList(elementTransformerExp);
-        }
-
-        protected int getArgumentsCount() {
-            return 1;
-        }
-
-        protected Expression getArgumentParameterValue(int argIdx) {
-            if (argIdx != 0) {
-                throw new IndexOutOfBoundsException();
-            }
-            return elementTransformerExp;
-        }
-
-        protected Expression getElementTransformerExp() {
-            return elementTransformerExp;
-        }
-
-        protected void cloneArguments(
-                Expression clone, String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) {
-            ((IntermediateStreamOperationLikeBuiltIn) clone).elementTransformerExp
-                    = elementTransformerExp.deepCloneWithIdentifierReplaced(
-                            replacedIdentifier, replacement, replacementState);
-        }
-
-        TemplateModel _eval(Environment env) throws TemplateException {
-            TemplateModel targetValue = target.eval(env);
-
-            final TemplateModelIterator targetIterator;
-            final boolean targetIsSequence;
-            {
-                if (targetValue instanceof TemplateCollectionModel) {
-                    targetIterator = isLazilyGeneratedResultEnabled()
-                            ? new LazyCollectionTemplateModelIterator((TemplateCollectionModel) targetValue)
-                            : ((TemplateCollectionModel) targetValue).iterator();
-                    targetIsSequence = targetValue instanceof LazilyGeneratedCollectionModel
-                            ? ((LazilyGeneratedCollectionModel) targetValue).isSequence()
-                            : targetValue instanceof TemplateSequenceModel;
-                } else if (targetValue instanceof TemplateSequenceModel) {
-                    targetIterator = new LazySequenceIterator((TemplateSequenceModel) targetValue);
-                    targetIsSequence = true;
-                } else {
-                    throw new NonSequenceOrCollectionException(target, targetValue, env);
-                }
-            }
-
-            return calculateResult(
-                    targetIterator, targetValue, targetIsSequence,
-                    evalElementTransformerExp(env),
-                    env);
-        }
-
-        private ElementTransformer evalElementTransformerExp(Environment env) throws TemplateException {
-            if (precreatedElementTransformer != null) {
-                return precreatedElementTransformer;
-            }
-
-            TemplateModel elementTransformerModel = elementTransformerExp.eval(env);
-            if (elementTransformerModel instanceof TemplateMethodModel) {
-                return new MethodElementTransformer((TemplateMethodModel) elementTransformerModel);
-            } else if (elementTransformerModel instanceof Macro) {
-                return new FunctionElementTransformer((Macro) elementTransformerModel, elementTransformerExp);
-            } else {
-                throw new NonMethodException(elementTransformerExp, elementTransformerModel, true, true, null, env);
-            }
-        }
-
-        /**
-         * @param lhoIterator Use this to read the elements of the left hand operand
-         * @param lho Maybe needed for operations specific to the built-in, like getting the size, otherwise use the
-         *           {@code lhoIterator} only.
-         * @param lhoIsSequence See {@link LazilyGeneratedCollectionModel#isSequence}
-         * @param elementTransformer The argument to the built-in (typically a lambda expression)
-         *
-         * @return {@link TemplateSequenceModel} or {@link TemplateCollectionModel} or {@link TemplateModelIterator}.
-         */
-        protected abstract TemplateModel calculateResult(
-                TemplateModelIterator lhoIterator, TemplateModel lho, boolean lhoIsSequence,
-                ElementTransformer elementTransformer,
-                Environment env) throws TemplateException;
-
-        /**
-         * Wraps the built-in argument that specifies how to transform the elements of the sequence, to hide the
-         * complexity of doing that.
-         */
-        interface ElementTransformer {
-            TemplateModel transformElement(TemplateModel element, Environment env) throws TemplateException;
-        }
-
-        /** {@link ElementTransformer} that wraps a local lambda expression. */
-        private static class LocalLambdaElementTransformer implements ElementTransformer {
-            private final LocalLambdaExpression elementTransformerExp;
-
-            public LocalLambdaElementTransformer(LocalLambdaExpression elementTransformerExp) {
-                this.elementTransformerExp = elementTransformerExp;
-            }
-
-            public TemplateModel transformElement(TemplateModel element, Environment env) throws TemplateException {
-                return elementTransformerExp.invokeLambdaDefinedFunction(element, env);
-            }
-        }
-
-        /** {@link ElementTransformer} that wraps a (Java) method call. */
-        private static class MethodElementTransformer implements ElementTransformer {
-            private final TemplateMethodModel elementTransformer;
-
-            public MethodElementTransformer(TemplateMethodModel elementTransformer) {
-                this.elementTransformer = elementTransformer;
-            }
-
-            public TemplateModel transformElement(TemplateModel element, Environment env)
-                    throws TemplateModelException {
-                Object result = elementTransformer.exec(Collections.singletonList(element));
-                return result instanceof TemplateModel ? (TemplateModel) result : env.getObjectWrapper().wrap(result);
-            }
-        }
-
-        /** {@link ElementTransformer} that wraps a call to an FTL function (things defined with {@code #function}). */
-        private static class FunctionElementTransformer implements ElementTransformer {
-            private final Macro templateTransformer;
-            private final Expression elementTransformerExp;
-
-            public FunctionElementTransformer(Macro templateTransformer, Expression elementTransformerExp) {
-                this.templateTransformer = templateTransformer;
-                this.elementTransformerExp = elementTransformerExp;
-            }
-
-            public TemplateModel transformElement(TemplateModel element, Environment env) throws
-                    TemplateException {
-                // #function-s were originally designed to be called from templates directly, so they expect an
-                // Expression as argument. So we have to create a fake one.
-                ExpressionWithFixedResult functionArgExp = new ExpressionWithFixedResult(
-                        element, elementTransformerExp);
-                return env.invokeFunction(env, templateTransformer,
-                        Collections.singletonList(functionArgExp),
-                        elementTransformerExp);
-            }
-        }
-
-    }
-
     static class filterBI extends IntermediateStreamOperationLikeBuiltIn {
 
         protected TemplateModel calculateResult(
diff --git a/src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java b/src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java
new file mode 100644
index 0000000..a270276
--- /dev/null
+++ b/src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java
@@ -0,0 +1,221 @@
+/*
+ * 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.Collections;
+import java.util.List;
+
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateMethodModel;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateSequenceModel;
+
+/**
+ * Built-in that's similar to a Java 8 Stream intermediate operation. To be on the safe side, by default these
+ * are eager, and just produce a {@link TemplateSequenceModel}. But when circumstances allow, they become lazy,
+ * similarly to Java 8 Stream intermediate operations. Another characteristic of these built-ins is that they
+ * usually accept lambda expressions as parameters.
+ */
+abstract class IntermediateStreamOperationLikeBuiltIn extends BuiltInWithParseTimeParameters {
+
+    private Expression elementTransformerExp;
+    private ElementTransformer precreatedElementTransformer;
+    private boolean lazilyGeneratedResultEnabled;
+
+    @Override
+    void bindToParameters(List<Expression> parameters, Token openParen, Token closeParen) throws ParseException {
+        // At the moment all built-ins of this kind requires 1 parameter.
+        if (parameters.size() != 1) {
+            throw newArgumentCountException("requires exactly 1", openParen, closeParen);
+        }
+        this.elementTransformerExp = parameters.get(0);
+        if (elementTransformerExp instanceof LocalLambdaExpression) {
+            LocalLambdaExpression localLambdaExp = (LocalLambdaExpression) elementTransformerExp;
+            checkLocalLambdaParamCount(localLambdaExp, 1);
+            // We can't do this with other kind of expressions, like a function or method reference, as they
+            // need to be evaluated on runtime:
+            precreatedElementTransformer = new LocalLambdaElementTransformer(localLambdaExp);
+        }
+    }
+
+    @Override
+    protected final boolean isLocalLambdaParameterSupported() {
+        return true;
+    }
+
+    @Override
+    final void enableLazilyGeneratedResult() {
+        this.lazilyGeneratedResultEnabled = true;
+    }
+
+    /** Tells if {@link #enableLazilyGeneratedResult()} was called. */
+    protected final boolean isLazilyGeneratedResultEnabled() {
+        return lazilyGeneratedResultEnabled;
+    }
+
+    @Override
+    protected final boolean isLazilyGeneratedTargetResultSupported() {
+        return true;
+    }
+
+    protected List<Expression> getArgumentsAsList() {
+        return Collections.singletonList(elementTransformerExp);
+    }
+
+    protected int getArgumentsCount() {
+        return 1;
+    }
+
+    protected Expression getArgumentParameterValue(int argIdx) {
+        if (argIdx != 0) {
+            throw new IndexOutOfBoundsException();
+        }
+        return elementTransformerExp;
+    }
+
+    protected Expression getElementTransformerExp() {
+        return elementTransformerExp;
+    }
+
+    protected void cloneArguments(
+            Expression clone, String replacedIdentifier, Expression replacement, ReplacemenetState replacementState) {
+        ((IntermediateStreamOperationLikeBuiltIn) clone).elementTransformerExp
+                = elementTransformerExp.deepCloneWithIdentifierReplaced(
+                        replacedIdentifier, replacement, replacementState);
+    }
+
+    TemplateModel _eval(Environment env) throws TemplateException {
+        TemplateModel targetValue = target.eval(env);
+
+        final TemplateModelIterator targetIterator;
+        final boolean targetIsSequence;
+        {
+            if (targetValue instanceof TemplateCollectionModel) {
+                targetIterator = isLazilyGeneratedResultEnabled()
+                        ? new LazyCollectionTemplateModelIterator((TemplateCollectionModel) targetValue)
+                        : ((TemplateCollectionModel) targetValue).iterator();
+                targetIsSequence = targetValue instanceof LazilyGeneratedCollectionModel
+                        ? ((LazilyGeneratedCollectionModel) targetValue).isSequence()
+                        : targetValue instanceof TemplateSequenceModel;
+            } else if (targetValue instanceof TemplateSequenceModel) {
+                targetIterator = new LazySequenceIterator((TemplateSequenceModel) targetValue);
+                targetIsSequence = true;
+            } else {
+                throw new NonSequenceOrCollectionException(target, targetValue, env);
+            }
+        }
+
+        return calculateResult(
+                targetIterator, targetValue, targetIsSequence,
+                evalElementTransformerExp(env),
+                env);
+    }
+
+    private ElementTransformer evalElementTransformerExp(Environment env) throws TemplateException {
+        if (precreatedElementTransformer != null) {
+            return precreatedElementTransformer;
+        }
+
+        TemplateModel elementTransformerModel = elementTransformerExp.eval(env);
+        if (elementTransformerModel instanceof TemplateMethodModel) {
+            return new MethodElementTransformer((TemplateMethodModel) elementTransformerModel);
+        } else if (elementTransformerModel instanceof Macro) {
+            return new FunctionElementTransformer((Macro) elementTransformerModel, elementTransformerExp);
+        } else {
+            throw new NonMethodException(elementTransformerExp, elementTransformerModel, true, true, null, env);
+        }
+    }
+
+    /**
+     * @param lhoIterator Use this to read the elements of the left hand operand
+     * @param lho Maybe needed for operations specific to the built-in, like getting the size, otherwise use the
+     *           {@code lhoIterator} only.
+     * @param lhoIsSequence See {@link LazilyGeneratedCollectionModel#isSequence}
+     * @param elementTransformer The argument to the built-in (typically a lambda expression)
+     *
+     * @return {@link TemplateSequenceModel} or {@link TemplateCollectionModel} or {@link TemplateModelIterator}.
+     */
+    protected abstract TemplateModel calculateResult(
+            TemplateModelIterator lhoIterator, TemplateModel lho, boolean lhoIsSequence,
+            ElementTransformer elementTransformer,
+            Environment env) throws TemplateException;
+
+    /**
+     * Wraps the built-in argument that specifies how to transform the elements of the sequence, to hide the
+     * complexity of doing that.
+     */
+    interface ElementTransformer {
+        TemplateModel transformElement(TemplateModel element, Environment env) throws TemplateException;
+    }
+
+    /** {@link ElementTransformer} that wraps a local lambda expression. */
+    private static class LocalLambdaElementTransformer implements ElementTransformer {
+        private final LocalLambdaExpression elementTransformerExp;
+
+        public LocalLambdaElementTransformer(LocalLambdaExpression elementTransformerExp) {
+            this.elementTransformerExp = elementTransformerExp;
+        }
+
+        public TemplateModel transformElement(TemplateModel element, Environment env) throws TemplateException {
+            return elementTransformerExp.invokeLambdaDefinedFunction(element, env);
+        }
+    }
+
+    /** {@link ElementTransformer} that wraps a (Java) method call. */
+    private static class MethodElementTransformer implements ElementTransformer {
+        private final TemplateMethodModel elementTransformer;
+
+        public MethodElementTransformer(TemplateMethodModel elementTransformer) {
+            this.elementTransformer = elementTransformer;
+        }
+
+        public TemplateModel transformElement(TemplateModel element, Environment env)
+                throws TemplateModelException {
+            Object result = elementTransformer.exec(Collections.singletonList(element));
+            return result instanceof TemplateModel ? (TemplateModel) result : env.getObjectWrapper().wrap(result);
+        }
+    }
+
+    /** {@link ElementTransformer} that wraps a call to an FTL function (things defined with {@code #function}). */
+    private static class FunctionElementTransformer implements ElementTransformer {
+        private final Macro templateTransformer;
+        private final Expression elementTransformerExp;
+
+        public FunctionElementTransformer(Macro templateTransformer, Expression elementTransformerExp) {
+            this.templateTransformer = templateTransformer;
+            this.elementTransformerExp = elementTransformerExp;
+        }
+
+        public TemplateModel transformElement(TemplateModel element, Environment env) throws
+                TemplateException {
+            // #function-s were originally designed to be called from templates directly, so they expect an
+            // Expression as argument. So we have to create a fake one.
+            ExpressionWithFixedResult functionArgExp = new ExpressionWithFixedResult(
+                    element, elementTransformerExp);
+            return env.invokeFunction(env, templateTransformer,
+                    Collections.singletonList(functionArgExp),
+                    elementTransformerExp);
+        }
+    }
+
+}