Merge remote-tracking branch 'origin/2.3-gae' into 2.3
diff --git a/build.xml b/build.xml
index b17349e..034f172 100644
--- a/build.xml
+++ b/build.xml
@@ -239,7 +239,7 @@
<delete dir="build/src-main-java-filtered" />
<mkdir dir="build/src-main-java-filtered" />
<copy toDir="build/src-main-java-filtered">
- <fileset dir="src/main/java" />
+ <fileset dir="src/main/java" excludes="**/AdhocTest*" />
</copy>
<replaceregexp
flags="gs" encoding="utf-8"
diff --git a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
index 8d47cbe..cff7e8e 100644
--- a/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
+++ b/src/ide-settings/IntelliJ-IDEA/Java-code-style-FreeMarker.xml
@@ -1,3 +1,21 @@
+<!--
+ 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.
+-->
<code_scheme name="FreeMarker" version="173">
<option name="LINE_SEPARATOR" value="
" />
<option name="WRAP_WHEN_TYPING_REACHES_RIGHT_MARGIN" value="true" />
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index 5e143b1..30c9c9d 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -388,21 +388,12 @@
throw new InternalError();
}
bi.key = key;
- if (bi.isLazilyGeneratedTargetResultSupported()) {
- target.enableLazilyGeneratedResult();
- }
- bi.target = target;
+ bi.setTarget(target);
return bi;
}
- /**
- * 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 IntermediateStreamOperationLikeBuiltIn#isLazilyGeneratedTargetResultSupported()}).
- */
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return false;
+ protected void setTarget(Expression target) {
+ this.target = target;
}
@Override
diff --git a/src/main/java/freemarker/core/BuiltInWithDirectCallOptimization.java b/src/main/java/freemarker/core/BuiltInWithDirectCallOptimization.java
new file mode 100644
index 0000000..a92ae43
--- /dev/null
+++ b/src/main/java/freemarker/core/BuiltInWithDirectCallOptimization.java
@@ -0,0 +1,31 @@
+/*
+ * 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;
+
+abstract class BuiltInWithDirectCallOptimization extends SpecialBuiltIn {
+
+ /**
+ * Called if the built-in is directly followed by a "(" (ignoring comments and white-space). This can be utilized
+ * for optimizations that only work correctly if the method returned by the built-in is a called immediately (as
+ * opposed to being stored in a variable and then called at an arbitrary later point in time).
+ */
+ protected abstract void setDirectlyCalled();
+
+}
diff --git a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
index 34ff1f0..cf70c31 100644
--- a/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/src/main/java/freemarker/core/BuiltInsForMultipleTypes.java
@@ -485,8 +485,9 @@
static class sizeBI extends BuiltIn {
@Override
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setTarget(Expression target) {
+ super.setTarget(target);
+ target.enableLazilyGeneratedResult();
}
private int countingLimit;
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index 2ba6b11..6d76e1d 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -143,8 +143,9 @@
static class firstBI extends BuiltIn {
@Override
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setTarget(Expression target) {
+ super.setTarget(target);
+ target.enableLazilyGeneratedResult();
}
@Override
@@ -181,11 +182,11 @@
}
- static class joinBI extends BuiltIn {
+ static class joinBI extends BuiltInWithDirectCallOptimization {
@Override
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setDirectlyCalled() {
+ target.enableLazilyGeneratedResult();
}
private class BIMethodForCollection implements TemplateMethodModelEx {
@@ -295,11 +296,11 @@
}
}
- static class seq_containsBI extends BuiltIn {
+ static class seq_containsBI extends BuiltInWithDirectCallOptimization {
@Override
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setDirectlyCalled() {
+ target.enableLazilyGeneratedResult();
}
private class BIMethodForCollection implements TemplateMethodModelEx {
@@ -367,15 +368,15 @@
}
- static class seq_index_ofBI extends BuiltIn {
+ static class seq_index_ofBI extends BuiltInWithDirectCallOptimization {
@Override
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setDirectlyCalled() {
+ target.enableLazilyGeneratedResult();
}
private class BIMethod implements TemplateMethodModelEx {
-
+
protected final TemplateSequenceModel m_seq;
protected final TemplateCollectionModel m_col;
protected final Environment m_env;
@@ -893,8 +894,9 @@
}
@Override
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setTarget(Expression target) {
+ super.setTarget(target);
+ target.enableLazilyGeneratedResult();
}
}
@@ -941,8 +943,9 @@
}
@Override
- protected boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setTarget(Expression target) {
+ super.setTarget(target);
+ target.enableLazilyGeneratedResult();
}
@Override
diff --git a/src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java b/src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java
index a270276..0d1378b 100644
--- a/src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java
+++ b/src/main/java/freemarker/core/IntermediateStreamOperationLikeBuiltIn.java
@@ -74,8 +74,9 @@
}
@Override
- protected final boolean isLazilyGeneratedTargetResultSupported() {
- return true;
+ protected void setTarget(Expression target) {
+ super.setTarget(target);
+ target.enableLazilyGeneratedResult();
}
protected List<Expression> getArgumentsAsList() {
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index a157871..363e8bc 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -2182,6 +2182,7 @@
ArrayList<Expression> args = null;
Token openParen;
Token closeParen;
+ MethodCall methodCall;
}
{
<BUILT_IN>
@@ -2236,6 +2237,7 @@
return result;
}
}
+
[
LOOKAHEAD({
result instanceof BuiltInWithParseTimeParameters
@@ -2265,10 +2267,26 @@
return result;
}
]
+
+ [
+ LOOKAHEAD(<OPEN_PAREN>, { result instanceof BuiltInWithDirectCallOptimization })
+ methodCall = MethodArgs(result)
+ {
+ ((BuiltInWithDirectCallOptimization) result).setDirectlyCalled();
+ return methodCall;
+ }
+ ]
+
{
+ if (result instanceof BuiltInWithDirectCallOptimization) {
+ // We had no (...)
+ return result;
+ }
+
// Should have already return-ed
throw new AssertionError("Unhandled " + SpecialBuiltIn.class.getName() + " subclass: " + result.getClass());
}
+
}
// Only supported as the argument of certain built-ins, so it's not called inside Expression.
diff --git a/src/main/resources/freemarker/included.html b/src/main/resources/freemarker/included.html
deleted file mode 100644
index 36c9254..0000000
--- a/src/main/resources/freemarker/included.html
+++ /dev/null
@@ -1,9 +0,0 @@
-<div class="code">
-<pre><code>SCRIPT_DIR="\
- $(\
- cd "$(dirname "${BASH_SRC_DIR[0]}")" \
- >/dev/null 2>&1 \
- && pwd\
- )"
-SCRIPT_NAME=$(basename $0)</code></pre>
-</div>
\ No newline at end of file
diff --git a/src/main/resources/freemarker/version.properties b/src/main/resources/freemarker/version.properties
index 720be33..fff63bd 100644
--- a/src/main/resources/freemarker/version.properties
+++ b/src/main/resources/freemarker/version.properties
@@ -56,11 +56,11 @@
# continue working without modification or recompilation.
# - When the major version number is increased, major backward
# compatibility violations are allowed, but still should be avoided.
-version=2.3.29-nightly_@timestampInVersion@
+version=2.3.29
# This exists as for Maven we use "-SNAPSHOT" for nightly releases,
# and no _nightly_@timestampInVersion@. For final releases it's the
# same as "version".
-mavenVersion=2.3.29-SNAPSHOT
+mavenVersion=2.3.29
# Version string that conforms to OSGi
# ------------------------------------
@@ -73,7 +73,7 @@
# 2.4.0.rc01
# 2.4.0.pre01
# 2.4.0.nightly_@timestampInVersion@
-versionForOSGi=2.3.29.nightly_@timestampInVersion@
+versionForOSGi=2.3.29.stable
# Version string that conforms to legacy MF
# -----------------------------------------
@@ -92,7 +92,7 @@
# "97 denotes "nightly", 98 denotes "pre", 99 denotes "rc" build.
# In general, for the nightly/preview/rc Y of version 2.X, the versionForMf is
# 2.X-1.(99|98).Y. Note the X-1.
-versionForMf=2.3.28.97
+versionForMf=2.3.29
# The date of the build.
# This should be automatically filled by the building tool (Ant).
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 3681a02..4a6e917 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -20,7 +20,11 @@
<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>
@@ -844,6 +848,14 @@
true of false depending on if <literal>user</literal> starts
with the letter <quote>J</quote> or not.</para>
</listitem>
+
+ <listitem>
+ <para><literal>animals?filter(it -> it.protected)</literal>
+ gives the list of protected animals. To list protected animals
+ only, you could use <literal><#list animals?filter(it ->
+ it.protected) as
+ animal><replaceable>...</replaceable></#list></literal>.</para>
+ </listitem>
</itemizedlist>
<para>Built-in applications can be chained, like
@@ -1373,7 +1385,10 @@
its size or retrieve its sub variables by index, but they can be
still listed with the <link
linkend="ref.directive.list"><literal>list</literal>
- directive</link>.</para>
+ directive</link>. Furthermore, very often they can only be
+ listed once. (If you are a Java programmer,
+ <quote>iterable</quote> would be a more fitting name than
+ collection.)</para>
</listitem>
</itemizedlist>
@@ -2159,6 +2174,12 @@
<literal>/=</literal>, <literal>%=</literal>,
<literal>++</literal>, <literal>--</literal></para>
</listitem>
+
+ <listitem>
+ <para><link linkend="dgui_template_exp_lambda">Local
+ lambdas</link>: <literal>x -> x + 1</literal>, <literal>(x,
+ y) -> x + y</literal></para>
+ </listitem>
</itemizedlist>
<para>See also: <link
@@ -3815,7 +3836,7 @@
<warning>
<para>If you have a composite expression after the
<literal>!</literal>, like <literal>1 + x</literal>,
- <emphasis>always</emphasis> use parenthesses, like
+ <emphasis>always</emphasis> use parentheses, like
<literal>${x!(1 + y)}</literal> or <literal>${(x!1) +
y)}</literal>, depending on which interpretation you meant.
That's needed because due to a programming mistake in FreeMarker
@@ -3998,6 +4019,59 @@
</note>
</section>
+ <section xml:id="dgui_template_exp_lambda">
+ <title>Local lambdas</title>
+
+ <indexterm>
+ <primary>lambda</primary>
+ </indexterm>
+
+ <para>FreeMarker doesn't support general purpose lambdas (unlike
+ Java). The usage of lambdas is restricted to the parameters of
+ certain <link linkend="dgui_template_exp_builtin">built-ins</link>,
+ like: <link
+ linkend="ref_builtin_filter"><literal>filter</literal></link>, <link
+ linkend="ref_builtin_map"><literal>map</literal></link>, <link
+ linkend="ref_builtin_take_while"><literal>take_while</literal></link>,
+ <link
+ linkend="ref_builtin_drop_while"><literal>drop_while</literal></link>.</para>
+
+ <para>The reason of this restriction is that FreeMarker doesn't
+ implement binding/capturing variables that are referred from the
+ lambda, instead it ensures that the evaluation of the lambda happens
+ before the enclosing variable scope is ended. Hence, and to
+ differentiate them from <quote>real</quote> lambdas, these are
+ called <emphasis>local</emphasis> lambdas.</para>
+
+ <para>The syntax of lambdas is like
+ <literal>(<replaceable>name1</replaceable>,
+ <replaceable>name2</replaceable>, <replaceable>...</replaceable>,
+ <replaceable>nameN</replaceable>) ->
+ <replaceable>expression</replaceable></literal>. If there's only a
+ single argument, the parentheses can be omitted:
+ <literal><replaceable>name1</replaceable> ->
+ <replaceable>expression</replaceable></literal>.</para>
+
+ <para>As the right side of the <literal>-></literal> is just a
+ single expression, if you need complex logic there, you probably
+ want to move that into a <link
+ linkend="ref.directive.function">function</link>, as the you can use
+ directives like <literal>if</literal>, <literal>list</literal>, etc.
+ In that case though, you don't need a lambda expression, as all
+ built-ins that support a lambda parameter, also support passing in a
+ function directly. For example, instead of <literal>seq?map(it ->
+ myMapper(it))</literal> you should just write
+ <literal>seq?map(myMapper)</literal>.</para>
+
+ <para>The argument specified in a lambda expression can hold the
+ missing (Java <literal>null</literal>) value. Reading a lambda
+ argument never falls back to higher scope, so a variable with
+ identical name will not interfere when accessing the lambda
+ parameter. Therefore something like <literal>seq?filter(it ->
+ it??)</literal>, which filters out missing element from the
+ sequence, will work reliably.</para>
+ </section>
+
<section xml:id="dgui_template_exp_parentheses">
<title>Parentheses</title>
@@ -4163,6 +4237,12 @@
<td><literal>||</literal></td>
</tr>
+
+ <tr>
+ <td>local lambda</td>
+
+ <td><literal>-></literal></td>
+ </tr>
</tbody>
</informaltable>
@@ -4999,10 +5079,11 @@
<literal>x</literal> in <literal><#list xs as
x><replaceable>...</replaceable></#list></literal>), and
they only exist between the start-tag and end-tag of the
- directive. They are only visible directly between these tags, not
- from macros or functions called from there. As such, they are
- quite similar to local variables, but they can't be assigned to
- directly.</para>
+ directive. (User defined directives, like macros, can also create
+ loop variables.) They are only visible directly between these
+ tags, not from macros or functions called from there. As such,
+ they are quite similar to local variables, but they can't be
+ assigned to directly.</para>
</listitem>
<listitem>
@@ -6441,14 +6522,17 @@
beginning of the application (possibly servlet) life-cycle:</para>
<programlisting role="unspecified">// Create your Configuration instance, and specify if up to what FreeMarker
-// version (here 2.3.27) do you want to apply the fixes that are not 100%
+// version (here 2.3.29) do you want to apply the fixes that are not 100%
// backward-compatible. See the Configuration JavaDoc for details.
-Configuration cfg = new Configuration(Configuration.VERSION_2_3_27);
+Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
// Specify the source where the template files come from. Here I set a
// plain directory for it, but non-file-system sources are possible too:
cfg.setDirectoryForTemplateLoading(new File("<replaceable>/where/you/store/templates</replaceable>"));
+// From here we will set the settings recommended for new projects. These
+// aren't the defaults for backward compatibilty.
+
// Set the preferred charset template files are stored in. UTF-8 is
// a good choice in most applications:
cfg.setDefaultEncoding("UTF-8");
@@ -6460,8 +6544,11 @@
// Don't log exceptions inside FreeMarker that it will thrown at you anyway:
cfg.setLogTemplateExceptions(false);
-// Wrap unchecked exceptions thrown during template processing into TemplateException-s.
-cfg.setWrapUncheckedExceptions(true);</programlisting>
+// Wrap unchecked exceptions thrown during template processing into TemplateException-s:
+cfg.setWrapUncheckedExceptions(true);
+
+// Do not fall back to higher scopes when reading a null loop variable:
+cfg.setFallbackOnNullLoopVariable(false);</programlisting>
<para>From now you should use this <emphasis>single</emphasis>
configuration instance (i.e., its a singleton). Note however that if a
@@ -6733,12 +6820,14 @@
/* You should do this ONLY ONCE in the whole application life-cycle: */
/* Create and adjust the configuration singleton */
- Configuration cfg = new Configuration(Configuration.VERSION_2_3_27);
+ Configuration cfg = new Configuration(Configuration.VERSION_2_3_29);
cfg.setDirectoryForTemplateLoading(new File("<replaceable>/where/you/store/templates</replaceable>"));
+ // Recommended settings for new projects:
cfg.setDefaultEncoding("UTF-8");
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
cfg.setLogTemplateExceptions(false);
cfg.setWrapUncheckedExceptions(true);
+ cfg.setFallbackOnNullLoopVariable(false);
/* ------------------------------------------------------------------------ */
/* You usually do these for MULTIPLE TIMES in the application life-cycle: */
@@ -12666,6 +12755,11 @@
</listitem>
<listitem>
+ <para><link
+ linkend="ref_builtin_drop_while">drop_while</link></para>
+ </listitem>
+
+ <listitem>
<para><link linkend="ref_builtin_esc">esc</link></para>
</listitem>
@@ -12689,6 +12783,10 @@
</listitem>
<listitem>
+ <para><link linkend="ref_builtin_filter">filter</link></para>
+ </listitem>
+
+ <listitem>
<para><link linkend="ref_builtin_first">first</link></para>
</listitem>
@@ -12860,6 +12958,10 @@
</listitem>
<listitem>
+ <para><link linkend="ref_builtin_map">map</link></para>
+ </listitem>
+
+ <listitem>
<para><link
linkend="ref_builtin_markup_string">markup_string</link></para>
</listitem>
@@ -13024,6 +13126,11 @@
</listitem>
<listitem>
+ <para><link
+ linkend="ref_builtin_take_while">take_while</link></para>
+ </listitem>
+
+ <listitem>
<para><link linkend="ref_builtin_then">then</link></para>
</listitem>
@@ -16958,6 +17065,439 @@
be of any type and value.</para>
</section>
+ <section xml:id="ref_builtin_drop_while">
+ <title>drop_while</title>
+
+ <indexterm>
+ <primary>drop_while built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>sequence</primary>
+
+ <secondary>drop while</secondary>
+ </indexterm>
+
+ <para>Returns a new sequence that contains the elements from the
+ input sequence starting with from the first element that does
+ <emphasis>not</emphasis> match the parameter predicate (condition).
+ After that, all elements are included, regardless if they match the
+ predicate or not. See the <link
+ linkend="ref_builtin_filter"><literal>filter</literal>
+ built-in</link> for more details about parameters, and other
+ details, but note that the condition in <literal>filter</literal>
+ has opposite meaning (what to keep, instead of what to drop).</para>
+
+ <para>Example and comparison with <literal>filter</literal>:</para>
+
+ <programlisting role="template"><#assign xs = [1, 2, -3, 4, -5, 6]>
+
+Drop while positive:
+<#list xs?drop_while(x -> x > 0) as x>${x} </#list>
+
+Filer for positives:
+<#list xs?filter(x -> x > 0) as x>${x} </#list></programlisting>
+
+ <programlisting role="output">Drop while positive:
+-3 4 -5 6
+
+Filer for positives:
+1 2 4 6 </programlisting>
+
+ <para>As you can see, <literal>take_while</literal> has stopped
+ dropping the elements once it ran into the first element that didn't
+ match the predicate (<literal>x > 0</literal>). On the other
+ hand, <literal>filter</literal> keeps the elements that match the
+ same predicate, and it doesn't stop.</para>
+
+ <para>See also: <link
+ linkend="ref_builtin_take_while"><literal>take_while</literal>
+ built-in</link></para>
+ </section>
+
+ <section xml:id="ref_builtin_filter">
+ <title>filter</title>
+
+ <indexterm>
+ <primary>filter built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>sequence</primary>
+
+ <secondary>filter</secondary>
+ </indexterm>
+
+ <note>
+ <para>This built-in is available since 2.3.29</para>
+ </note>
+
+ <para>Returns a new sequence that only contains the elements for
+ which the parameter condition (the predicate) returns
+ <literal>true</literal>. For example:</para>
+
+ <programlisting role="template"><#assign xs = [1, -2, 3, 4, -5]>
+Positives:
+<#list xs?<emphasis>filter(x -> x > 0)</emphasis> as x>${x} </#list>
+Negatives:
+<#list xs?<emphasis>filter(x -> x < 0)</emphasis> as x>${x} </#list></programlisting>
+
+ <programlisting role="output">Positives:
+1 3 4
+Negatives:
+-2 -5 </programlisting>
+
+ <para>This built-in has a single required parameter, the predicate
+ (filter condition, what to keep). The predicate can be specified in
+ 3 ways:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>As a single argument <link
+ linkend="dgui_template_exp_lambda">lambda expression</link>:
+ <literal><replaceable>element</replaceable> ->
+ <replaceable>predicate</replaceable></literal>. In that,
+ <literal><replaceable>element</replaceable></literal> is the
+ variable name with which you can refer to the current element in
+ the <literal><replaceable>predicate</replaceable></literal>, and
+ the <literal><replaceable>predicate</replaceable></literal> is
+ an arbitrarily complex <link
+ linkend="dgui_template_exp">expression</link> that must return a
+ boolean value (<literal>true</literal> or
+ <literal>false</literal>). An example this was shown above. Note
+ again the predicates can be arbitrarily complex, like the
+ predicate in <literal>products?filter(product ->
+ product.discounted &&
+ !user.hasBought(product))</literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para>As a <link
+ linkend="ref_directive_function">function</link> or method that
+ has a single argument, and returns boolean. For example, the
+ <quote>Negatives</quote> example above could be implemented like
+ this:</para>
+
+ <programlisting role="template"><#function negative(x)>
+ <#return x < 0>
+</#function>
+
+<replaceable>...</replaceable>
+
+Negatives:
+<#list xs<emphasis>?filter(negative)</emphasis> as x>${x} </#list></programlisting>
+
+ <para>Note how we just referred to the function by name, and did
+ not call it. Similarly, if you have a Java object called
+ <literal>utils</literal> in the data-model, and it has a
+ <literal>boolean isNegative(Number n)</literal> method, then you
+ could use that like
+ <literal>xs?filter(utils.isNegative)</literal>.</para>
+ </listitem>
+ </itemizedlist>
+
+ <note>
+ <para>Remember, the condition (predicate) that you specify tells
+ <emphasis>what to keep</emphasis>, not what to filter out! That
+ is, the element will be in the result sequence when you return
+ <literal>true</literal>, not when you return
+ <literal>false</literal>. (It's like the <literal>WHERE</literal>
+ condition in SQL, if you know that.)</para>
+ </note>
+
+ <para>While <literal>filter</literal> is most often used in the
+ <link linkend="ref.directive.list"><literal>list</literal>
+ directive</link>, naturally it can be used anywhere where a filtered
+ sequence is needed, and so this works as well:</para>
+
+ <programlisting role="template"><#assign negatives = xs?filter(x -> x < 0)>
+Negatives:
+<#list negatives as x>${x} </#list></programlisting>
+
+ <para>Note however, that for a very long sequences, the above
+ solution can consume significantly more memory. That's because
+ <literal><list
+ <replaceable>seq</replaceable>?filter(<replaceable>pred</replaceable>)
+ <replaceable>...</replaceable>></literal> is optimized to do
+ filtering without building an intermediate filtered sequence, while
+ the n above example, <literal>assign</literal> will first build the
+ whole filtered sequence in memory, and we pass that filtered
+ sequence later to <literal>list</literal>. But again, this only
+ matters for very long sequences.</para>
+
+ <para>See also: <link
+ linkend="ref_builtin_take_while"><literal>take_while</literal>
+ built-in</link>, <link
+ linkend="ref_builtin_drop_while"><literal>drop_while</literal>
+ built-in</link></para>
+
+ <simplesect xml:id="topic.filterLazyEval">
+ <title>Lazy evaluation and its consequences</title>
+
+ <note>
+ <para>Identical rules apply to these built-ins as well: <link
+ linkend="ref_builtin_map"><literal>map(<replaceable>mapper</replaceable>)</literal></link>,
+ <link
+ linkend="ref_builtin_take_while"><literal>take_while(<replaceable>predicate</replaceable>)</literal></link>,
+ <link
+ linkend="ref_builtin_drop_while"><literal>drop_while(<replaceable>predicate</replaceable>)</literal></link>.</para>
+ </note>
+
+ <para>To optimize processing, <literal>filter</literal> might
+ delays fetching the elements of the input sequence, and applying
+ the predicate on them. But it's guaranteed that those operations
+ are not delayed past the point where the execution of the
+ directive or interpolation, whose parameter contains the
+ <literal><replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)</literal>,
+ is finished. Some examples:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>In the case of <literal><list
+ <replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)
+ <replaceable>...</replaceable>><replaceable>nested
+ content</replaceable></#list></literal>, when the
+ execution enters the <literal><replaceable>nested
+ content</replaceable></literal>, it's not true that all
+ elements of <literal><replaceable>seq</replaceable></literal>
+ was already consumed and filtered. Consuming and filtering
+ <literal><replaceable>seq</replaceable></literal> is instead
+ done bit by bit as <literal>list</literal> repeats the nested
+ content. But it's guaranteed that past the
+ <literal></#list></literal> tag (the end of the
+ execution of the <literal>list</literal> directive), there are
+ no delayed readings of
+ <literal><replaceable>seq</replaceable></literal>, or delayed
+ evaluation of the
+ <literal><replaceable>predicate</replaceable></literal>. So
+ avoid changing a such variable (or other system state) in the
+ nested content of <literal>list</literal>, which influences
+ the result of the
+ <literal><replaceable>predicate</replaceable></literal>. Doing
+ so could change the filtering for the rest of the
+ <literal><replaceable>seq</replaceable></literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para>In the case of <literal><#assign
+ <replaceable>filteredSeq</replaceable> =
+ <replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)></literal>
+ it's guaranteed that all elements of
+ <literal><replaceable>seq</replaceable></literal> were
+ processed, and thus
+ <literal><replaceable>predicate</replaceable></literal> won't
+ be evaluated after the <literal>assign</literal>
+ directive.</para>
+ </listitem>
+
+ <listitem>
+ <para>In the case of
+ <literal>${<replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)?join(',
+ ')}</literal> it's guaranteed that all elements of
+ <literal><replaceable>seq</replaceable></literal> were
+ processed, and thus
+ <literal><replaceable>predicate</replaceable></literal> won't
+ be evaluated after the <literal>assign</literal>
+ directive.</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>Inside <link linkend="dgui_template_exp">expressions</link>
+ however, there's no promise regarding when the elements are
+ consumed and when the predicate is evaluated. Like in the case of
+ <literal><replaceable>seq</replaceable>?filter(<replaceable>predicate1</replaceable>)?filter(<replaceable>predicate2</replaceable>)</literal>,
+ it's not guaranteed that
+ <literal><replaceable>predicate1</replaceable></literal> will only
+ be evaluated before
+ <literal><replaceable>predicate2</replaceable></literal>. (Most
+ likely they will be called alternately:
+ <literal><replaceable>predicate1</replaceable></literal> for the
+ 1st element, then
+ <literal><replaceable>predicate2</replaceable></literal> for the
+ 1st element, then
+ <literal><replaceable>predicate1</replaceable></literal> for the
+ 2nd element, then
+ <literal><replaceable>predicate2</replaceable></literal> for the
+ 2nd element, and so on.)</para>
+
+ <para>If you pass a filtered sequence to a
+ <emphasis>custom</emphasis> directive (a macro) or function or
+ method, as in <literal><@<replaceable>myMacro</replaceable>
+ <replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)
+ /></literal> or
+ <literal><replaceable>myFunction</replaceable>(<replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>))</literal>,
+ then it's guaranteed that the filtering is not delayed past the
+ point when the custom directive/function/method is invoked. That
+ is, your macro/function/method will aways receive a fully
+ constructed filtered sequence.</para>
+
+ <para>Also note that in it's <emphasis>not</emphasis> guaranteed
+ that all elements of the input sequence will be read, and
+ therefore that the predicate will be evaluated for all elements.
+ Some examples of such cases:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para>You may <link
+ linkend="ref.directive.list.break"><literal>break</literal></link>
+ out from <literal><list
+ <replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)
+ <replaceable>...</replaceable>></literal> before it reaches
+ the last element, in which case the rest of the
+ <literal><replaceable>seq</replaceable></literal> elements
+ won't be fetched and filtered.</para>
+ </listitem>
+
+ <listitem>
+ <para>In the case of
+ <literal><replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)[2]</literal>,
+ which reads the 3rd element of the filtered sequence,
+ FreeMarker stops fetching and filtering the elements of
+ <literal><replaceable>seq</replaceable></literal> when we have
+ found the 3rd element that matches the
+ <literal><replaceable>predicate</replaceable></literal>.</para>
+ </listitem>
+
+ <listitem>
+ <para>In the case of
+ <literal><replaceable>seq</replaceable>?filter(<replaceable>predicate</replaceable>)?size
+ != 0</literal>, which tells whether the filtered sequence is
+ non-empty, we stop fetching and filtering the elements of
+ <literal><replaceable>seq</replaceable></literal> when we have
+ found the 1st element that matches the
+ <literal><replaceable>predicate</replaceable></literal>.
+ (That's certainly surprising as <literal>?size</literal> needs
+ to process the whole sequence to tell the size. But in this
+ case FreeMarker notices that we don't really need the exact
+ size.)</para>
+ </listitem>
+ </itemizedlist>
+
+ <para>If you are a Java programmer, note how the
+ <literal>filter</literal> built-in differs from Java
+ <literal>Stream.filter</literal>. <literal>Stream.filter</literal>
+ is <quote>lazy</quote>, while FreeMarker <literal>filter</literal>
+ is basically <quote>eager</quote>, and is only <quote>lazy</quote>
+ in special cases, and within a limited scope. Thus, unlike in
+ Java, calling <literal>filter</literal> is not always free. In
+ particular, if you assign a filtered sequence to a variable, or
+ pass it to a custom directive/function/method, the filtered
+ sequence will be created eagerly.</para>
+ </simplesect>
+
+ <simplesect xml:id="topic.filterLongInput">
+ <title>Filtering very long input that you don't hold in
+ memory</title>
+
+ <note>
+ <para>Identical rules apply to these built-ins as well: <link
+ linkend="ref_builtin_map"><literal>map(<replaceable>mapper</replaceable>)</literal></link>,
+ <link
+ linkend="ref_builtin_take_while"><literal>take_while(<replaceable>predicate</replaceable>)</literal></link>,
+ <link
+ linkend="ref_builtin_drop_while"><literal>drop_while(<replaceable>predicate</replaceable>)</literal></link>.</para>
+ </note>
+
+ <para>Some applications, particularly those that render huge
+ tables, use <link linkend="dgui_datamodel_container">sequence-like
+ values</link> in the data-model that are not held in memory at
+ once, instead they are like a stream of elements that you can only
+ read in the order as they are given to you (on the Java side these
+ are <literal>java.util.Iterator</literal>-s, or
+ <literal>java.util.Iterables</literal>, or the like). These will
+ have <quote>collection</quote> type in the template language,
+ which is like a restricted sequence.</para>
+
+ <para><literal>filter</literal> works with collection input too.
+ As you have seen earlier, <literal>filter</literal> might stores
+ the entire filtered sequence in the memory, which in this case
+ sounds concerning, because if the input was too big to fit into
+ the memory (hence it wasn't exposed as a sequence), then the
+ filtered collection can be too big as well. For that reason, if
+ the input is not a sequence (but a collection),
+ <literal>filter</literal> never collects its result into the
+ memory, and never fetches and processes the input elements until
+ they are really needed (<quote>lazy</quote> behavior). Furthermore
+ the result of <literal>filter</literal> is then a collection, not
+ a sequence, therefor sequence operations (like
+ <literal><replaceable>seq</replaceable>[<replaceable>index</replaceable>]</literal>)
+ will not work on it.</para>
+
+ <para>Unlike with sequence input, any operation that would cause
+ collecting the whole filtered result into the memory will now
+ fail. Let's see that through examples. Let's say we have
+ <literal>hugeTable</literal> in the data-model, which is not a
+ sequence, but still a collection (probably an
+ <literal>Iterator</literal> in Java). Then, consider:</para>
+
+ <programlisting role="template"><#-- Works: -->
+<#list hugeTable?filter(<replaceable>predicate</replaceable>) as row><replaceable>nested content</replaceable></#list></programlisting>
+
+ <para>This works fine, since <literal>list</literal> doesn't
+ require collecting the result into the memory</para>
+
+ <para>Consider this:</para>
+
+ <programlisting role="template"><#-- Fails if hugeTable is not a sequence, just a collection: -->
+<#assign filteredHugeTable = hugeTable?filter(<replaceable>predicate</replaceable>)></programlisting>
+
+ <para>This fails, as filtering can't be postponed beyond the
+ containing directive (<literal>assign</literal>), so FreeMareker
+ had to put the entire filtered result into
+ <literal>filteredHugeTable</literal>. If, however, you know that
+ <literal>filteredHugeTable</literal> won't be too big, you can
+ explicitly collect the result into a sequence via the <link
+ linkend="ref_builtin_sequence"><literal>sequence</literal>
+ built-in</link>:</para>
+
+ <programlisting role="template"><#-- Works, but be sure filtredHugeTable fits into the memory: -->
+<#assign filteredHugeTable = hugeTable?filter(predicate)<emphasis>?sequence</emphasis>></programlisting>
+
+ <para>Naturally, applying the <literal>sequence</literal> built-in
+ allows all sequence operations, such as
+ <literal><replaceable>seq</replaceable>[<replaceable>index</replaceable>]</literal>,
+ <literal><replaceable>seq</replaceable>[<replaceable>range</replaceable>]</literal>,
+ or
+ <literal><replaceable>seq</replaceable>?<replaceable>size</replaceable></literal>.
+ If these operations are directly applied on a sequence that was
+ converted from a collection, then FreeMarker optimizes out
+ actually creating the sequence in memory. So these won't consume
+ much memory regardless of the size of the filtered
+ <literal>hugeTable</literal>:</para>
+
+ <itemizedlist>
+ <listitem>
+ <para><literal>hugeTable?filter(<replaceable>predicate</replaceable>)?sequence[index]</literal>:
+ FreeMarker will just fetch and drop the elements till it
+ reaches the element at the desired position.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>hugeTable?filter(<replaceable>predicate</replaceable>)?sequence[0..9]</literal>:
+ FreeMarker will just collect the first 10 elements.</para>
+ </listitem>
+
+ <listitem>
+ <para><literal>hugeTable?filter(<replaceable>predicate</replaceable>)?sequence?size</literal>:
+ In this case the whole <literal>hugeTable</literal> will be
+ fetched, which is possibly slow, but the fetched elements are
+ still not collected into the memory, as they only need to be
+ counted.</para>
+ </listitem>
+ </itemizedlist>
+ </simplesect>
+
+ <simplesect xml:id="topic.filterMissing">
+ <title>Filtering missing (null) values</title>
+
+ <para>The argument to a lambda expression can hold the missing
+ (Java <literal>null</literal>) value, and reading such value will
+ not fall back to a higher scope. Thus, something like
+ <literal>seq?filter(it -> it??)</literal>, which filters out
+ missing element from the sequence, will work reliably.</para>
+ </simplesect>
+ </section>
+
<section xml:id="ref_builtin_first">
<title>first</title>
@@ -17057,6 +17597,40 @@
die with error if the sequence is empty.</para>
</section>
+ <section xml:id="ref_builtin_map">
+ <title>map</title>
+
+ <indexterm>
+ <primary>map built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>sequence</primary>
+
+ <secondary>filter</secondary>
+ </indexterm>
+
+ <para>Returns an new sequence where all elements are replaced with
+ the result of the parameter lambda, function, or method. For
+ example, you have a list of user objects in
+ <literal>users</literal>, but instead you need a list of user names
+ in variable, then you could do this:</para>
+
+ <programlisting role="template"><#assign userNames = users?map(user -> user.name)></programlisting>
+
+ <para>The parameter work like the parameter of the with <link
+ linkend="ref_builtin_filter"><literal>filter</literal>
+ built-in</link> (so see there), except that the
+ lambda/function/method you specify can return values of any
+ type.</para>
+
+ <para>Regarding lazy evaluation, and handling of very long inputs,
+ it also <link linkend="topic.filterLazyEval">works on the same
+ way</link> as the <link
+ linkend="ref_builtin_filter"><literal>filter</literal>
+ built-in</link>.</para>
+ </section>
+
<section xml:id="ref_builtin_min_max">
<title>min, max</title>
@@ -17110,11 +17684,6 @@
</indexterm>
<note>
- <para>This built-in is available since FreeMarker 2.3.1. It
- doesn't exist in 2.3.</para>
- </note>
-
- <note>
<para>The <literal>seq_</literal> prefix is required in the
built-in name to differentiate it from the <link
linkend="ref_builtin_contains"><literal>contains</literal>
@@ -17406,6 +17975,51 @@
- Fox, Amanda: 25 years old
- Smith, Joe: 40 years old</programlisting>
</section>
+
+ <section xml:id="ref_builtin_take_while">
+ <title>take_while</title>
+
+ <indexterm>
+ <primary>take_while built-in</primary>
+ </indexterm>
+
+ <indexterm>
+ <primary>sequence</primary>
+
+ <secondary>take while</secondary>
+ </indexterm>
+
+ <para>Returns a sequence that only contains the elements of the
+ input sequence which are before the first element that doesn't match
+ the parameter predicate (filter condition). This is very similar to
+ the <link linkend="ref_builtin_filter"><literal>filter</literal>
+ built-in</link>, so see further details there.</para>
+
+ <para>Example and comparison with <literal>filter</literal>:</para>
+
+ <programlisting role="template"><#assign xs = [1, 2, -3, 4, -5, 6]>
+
+Take while positive:
+<#list xs<emphasis>?take_while</emphasis>(x -> x > 0) as x>${x} </#list>
+
+Filer for positives:
+<#list xs?filter(x -> x > 0) as x>${x} </#list></programlisting>
+
+ <programlisting role="output">Take while positive:
+1 2
+
+Filer for positives:
+1 2 4 6 </programlisting>
+
+ <para>As you can see, <literal>take_while</literal> has stopped at
+ the first number that didn't match the predicate (<literal>x >
+ 0</literal>), while <literal>filter</literal> has continued finding
+ further matches.</para>
+
+ <para>See also: <link
+ linkend="ref_builtin_drop_while"><literal>drop_while</literal>
+ built-in</link></para>
+ </section>
</section>
<section xml:id="ref_builtins_hash">
@@ -18994,8 +19608,9 @@
support operations like <literal>xs[index]</literal> and
<literal>xs?size</literal>. Also, the resulting value is listable
for multiple times, even if the original value was backed by a
- <literal>java.util.Iterator</literal>. This built-in is typically
- used to work around data-model problems, in case you can't fix the
+ <literal>java.util.Iterator</literal> (which gives error when you
+ try to list it for the 2nd time). This built-in is typically used to
+ work around data-model problems, in case you can't fix the
data-model itself. If you can, always fix the data-model instead
(give a <literal>java.util.List</literal> or array to the template
instead of a more restricted object, like a
@@ -19006,10 +19621,13 @@
returns that as is. If the value is not something that the <link
linkend="ref.directive.list"><literal>list</literal>
directive</link> could list, then template processing will be
- aborted with error. Otherwise, it fetches all the values, and stores
- them into a sequence. Be careful if you can have a huge number of
- items, as all of them will be held in memory on the same
- time.</para>
+ aborted with error. Otherwise, it usually fetches all the values,
+ and stores them into a sequence. Be careful if you can have a huge
+ number of items, as all of them will be held in memory on the same
+ time. However, in some special cases fetching and/or storing all
+ elements is avoided; see about the <link
+ linkend="ref_builtin_sequence_optimizations">optimizations</link>
+ later.</para>
<para>You should convert a value with <literal>sequence</literal>
only once. If you need the resulting sequence at multiple places,
@@ -19032,6 +19650,39 @@
Again:
<#list usersSeq as user>...</#list>
</programlisting>
+
+ <simplesect xml:id="ref_builtin_sequence_optimizations">
+ <title>Optimizations</title>
+
+ <para>Since version 2.3.29, if the result of the
+ <literal>sequence</literal> built-in is directly the input of to
+ the <link
+ linkend="dgui_template_exp_var_sequence"><literal>[<replaceable>index</replaceable>]</literal></link>
+ or <link
+ linkend="dgui_template_exp_seqenceop_slice"><literal>[<replaceable>range</replaceable>]</literal></link>
+ operator, or of <literal>?size</literal>, or of
+ <literal>?first</literal>, or a chain of these operations, then
+ the elements will not be collected into the memory, and only as
+ many elements as strictly necessary will be fetched. For example
+ <literal>anIterator?sequence[1]</literal> will just fetch the
+ first 2 items (instead of building a sequence that contains all
+ the elements, and then getting the 2nd element from that). Or, if
+ you write <literal>anIterator?sequence?size</literal>, it will
+ just skip through all elements to count them, but won't store them
+ in memory.</para>
+
+ <para>The optimizations will only work within the same chain of
+ built-in calls, so for example in <literal><#assign seq =
+ anIterator?sequence>${seq[1]}</literal> the
+ <literal>?sequence</literal> step will collect all the elements
+ into the memory, as <literal>anIterator?sequence</literal> and
+ <literal>seq[1]</literal> are separated. On the other hand, the
+ optimizations will work in
+ <literal>anIterator?sequence[10..]?size</literal>, as both
+ <literal>[<replaceable>range</replaceable>]</literal> and
+ <literal>?size</literal> supports it, and they are directly
+ chained together.</para>
+ </simplesect>
</section>
</section>
</chapter>
@@ -21081,9 +21732,9 @@
<replaceable>Part repeated for each key-value pair</replaceable>
</#list></literal></programlisting>
- <para>But these are just cases of the generic forms, which are shown
- below. Note that for simplicity we only show the generic forms for
- sequence listing; simply replace <quote><literal>as
+ <para>But these are just special cases of the generic forms, which
+ are shown below. Note that for simplicity we only show the generic
+ forms for sequence listing; simply replace <quote><literal>as
<replaceable>item</replaceable></literal></quote> with
<quote><literal>as <replaceable>key</replaceable>,
<replaceable>value</replaceable></literal></quote> to get the
@@ -21396,6 +22047,16 @@
<primary>break directive</primary>
</indexterm>
+ <note>
+ <para><literal>break</literal> is deprecated for most use cases,
+ as it doesn't work well with <literal><#sep></literal> and
+ <literal><replaceable>item</replaceable>?has_next</literal>.
+ Instead, use <link
+ linkend="ref_builtin_take_while"><literal><replaceable>sequence</replaceable>?take_while(<replaceable>predicate</replaceable>)</literal></link>
+ to cut the sequence before you list it. See also examples <link
+ linkend="ref_list_skipping">here.</link></para>
+ </note>
+
<para>You can exit the iteration at any point with the
<literal>break</literal> directive. For example:</para>
@@ -21436,10 +22097,11 @@
<literal>break</literal>-able directive.</para>
<para>Using <literal>break</literal> together with
- <literal>sep</literal> is generally a bad idea, as
- <literal>sep</literal> can't know if you will skip the rest of
- items with <literal>break</literal>, and then you end up with a
- separator after the item printed last.</para>
+ <literal>sep</literal> or <literal>?has_next</literal> is
+ generally a bad idea, as these can't know if you will skip the
+ rest of items with a <literal>break</literal>. To solve such
+ situations see <link linkend="ref_list_skipping">these
+ examples</link>.</para>
<para>Just like <literal>else</literal> and
<literal>items</literal>, <literal>break</literal> must be
@@ -21457,8 +22119,22 @@
</indexterm>
<note>
+ <para><literal>continue</literal> is deprecated for most use
+ cases, as it doesn't work well with
+ <literal><#sep></literal>,
+ <literal><replaceable>item</replaceable>?has_next</literal>,
+ <literal><replaceable>item</replaceable>?count</literal>,
+ <literal><replaceable>item</replaceable>?index</literal>,
+ <literal><replaceable>item</replaceable>?item_parity</literal>,
+ etc. Instead, use <link
+ linkend="ref_builtin_filter"><literal><replaceable>sequence</replaceable>?filter(<replaceable>predicate</replaceable>)</literal></link>
+ to remove unwanted elements. See also examples <link
+ linkend="ref_list_skipping">here.</link></para>
+ </note>
+
+ <note>
<para>The <literal>continue</literal> directive exists since
- FreeMarker 2.3.27</para>
+ FreeMarker 2.3.27.</para>
</note>
<para>You can skip the rest of the iteration body (the section
@@ -21496,10 +22172,12 @@
<para>When you call <literal>continue</literal>, the
<literal>sep</literal> directive will not be executed for that
iteration. Using <literal>continue</literal> together with
- <literal>sep</literal> is generally a bad idea, as
- <literal>sep</literal> can't know if you will skip the rest of the
- items, and then you end up with a separator after the item printed
- last.</para>
+ <literal>sep</literal> is generally a bad idea anyway, also
+ <literal>?has_next</literal>, <literal>?count</literal>,
+ <literal>?index</literal>, <literal>?item_parity</literal>, etc.
+ will not work as you certainly wanted if you completely skip
+ items. To solve such situations see <link
+ linkend="ref_list_skipping">these examples</link>.</para>
<para>Just like <literal>break</literal>,
<literal>continue</literal> must be literally inside body of the
@@ -21574,6 +22252,78 @@
1}</literal>.</para>
</section>
+ <section xml:id="ref_list_skipping">
+ <title>Skipping items conditionally</title>
+
+ <para>If you need to skip certain element in a list, it's
+ generally a bad idea to use <link
+ linkend="ref.directive.if"><literal>if</literal> directive</link>
+ for that, because then <literal><#sep></literal>,
+ <literal><replaceable>item</replaceable>?has_next</literal>,
+ <literal><replaceable>item</replaceable>?count</literal>,
+ <literal><replaceable>item</replaceable>?index</literal>,
+ <literal><replaceable>item</replaceable>?item_parity</literal>,
+ etc., will not be usable, as FreeMarker doesn't know what items
+ were and will be actually displayed. Instead, you should try to
+ remove the unwanted items from the sequence that you will list,
+ and then list it (since 2.3.29). Here are some typical examples
+ with and without <literal>if</literal>.</para>
+
+ <simplesect>
+ <title>Filtering</title>
+
+ <para>In this example, you want to show the recommended products
+ from <literal>products</literal>. Here's the wrong solution with
+ <literal>if</literal>:</para>
+
+ <programlisting role="template"><#-- WRONG solution! The row parity classes will be possibly messed up: -->
+<#list products as product>
+ <#<emphasis>if product.recommended</emphasis>>
+ <div class="${product<emphasis>?item_parity</emphasis>}Row">${product.name}</div>
+ </#if>
+</#list></programlisting>
+
+ <para>Here's the good solution that uses the <link
+ linkend="ref_builtin_filter"><literal>filter</literal>
+ built-in</link>:</para>
+
+ <programlisting role="template"><#-- Good solution: -->
+<#list products<emphasis>?filter(p -> p.recommended)</emphasis> as product>
+ <div class="${product?item_parity}Row">${product.name}</div>
+</#list></programlisting>
+ </simplesect>
+
+ <simplesect>
+ <title>Stop listing when a certain element is found</title>
+
+ <para>Let's say you have a list of lines in
+ <literal>lines</literal>, and you need to stop at the first
+ empty line (if there's any). Furthermore you need to
+ <literal><br></literal> between the elements. Here's the
+ wrong solution with <literal>if</literal> and
+ <literal>break</literal>:</para>
+
+ <programlisting role="template"><#-- WRONG solution! <br> might be added after the last printed line: -->
+<#list lines as line>
+ <#if line == ''>
+ <#break>
+ </#if>
+ ${line}<#sep><br>
+</#list></programlisting>
+
+ <para>Here's the good solution that uses the <link
+ linkend="ref_builtin_take_while"><literal>take_while</literal>
+ built-in</link> (note that the condition is inverted compared to
+ the <literal>if</literal>+<literal>break</literal>
+ solution):</para>
+
+ <programlisting role="template"><#-- Good solution: -->
+<#list lines?take_while(line -> line != '') as line>
+ ${line}<#sep><br>
+</#list></programlisting>
+ </simplesect>
+ </section>
+
<section xml:id="ref_list_nesting">
<title>Nesting loops into each other</title>
@@ -21617,6 +22367,40 @@
Outer again: 2</programlisting>
</section>
+ <section xml:id="ref_list_missing_element">
+ <title>Treatment of missing (null) elements</title>
+
+ <para>As you know by now, the <literal>list</literal> directive
+ will repeat its nested content for each element of the listed
+ value. However, it's technically possible that you have holes
+ (missing values, Java <literal>null</literal>-s) in the list of
+ elements. The nested content will executed for these
+ <quote>holes</quote> as well, but then the loop variable (the
+ variable whose name you specify after the <literal>as</literal>
+ keyword) will be missing. When FreeMarker finds that there's no
+ variable with the given name in the loop variable scope, it will
+ just fall back to a higher scopes to find it. However, this
+ fallback behavior can be problematic in this case.
+ Consider:</para>
+
+ <programlisting role="template"><#list xs as x>
+ ${x!'Missing'}
+</#list></programlisting>
+
+ <para>Here, the intent of the author is to print
+ <quote>Missing</quote> for the missing elements (hopes) of
+ <literal>xs</literal>. But if accidentally there's an
+ <literal>x</literal> variable in a higher scope, like in the
+ data-model, then <literal>x</literal> will evaluate to that in
+ case the currently listed element is missing, and so it won't
+ default to <quote>Missing</quote>. To solve that, the programmers
+ should set the <literal>fallback_on_null_loop_variable</literal>
+ configuration setting to <literal>false</literal>. (Unfortunately,
+ the default must be <literal>true</literal> for backward
+ compatibility.) In that case no fallback will occur if a loop
+ variable is missing.</para>
+ </section>
+
<section xml:id="ref_list_java_notes">
<title>Notes for Java programmers</title>
@@ -27888,31 +28672,38 @@
<section xml:id="versions_2_3_29">
<title>2.3.29</title>
- <para>Release date: [FIXME]</para>
+ <para>Release date: 2019-08-09 + release process</para>
<section>
<title>Changes on the FTL side</title>
<itemizedlist>
<listitem>
- <para>Added new built-ins:
- <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
+ <para>Added new built-ins: <link
+ linkend="ref_builtin_filter"><literal>?filter(<replaceable>predicate</replaceable>)</literal></link>,
+ <link
+ linkend="ref_builtin_map"><literal>?map(<replaceable>mapper</replaceable>)</literal></link>,
+ <link
+ linkend="ref_builtin_take_while"><literal>?take_while(<replaceable>predicate</replaceable>)</literal></link>,
+ <link
+ linkend="ref_builtin_drop_while"><literal>?drop_while(<replaceable>predicate</replaceable>)</literal></link>.
+ These allow using lambda expressions, like
<literal>users?filter(user -> user.superuser)</literal> or
<literal>users?map(user -> user.name)</literal>, or accept a
- functions/method as parameter. (Lambda expressions are also new
+ functions/method as parameter. Lambda expressions are also new
in this release, but they can only be used in said built-ins, so
- they aren't like in Java for example.) These built-ins are
- generally eager, that is, they immediately build a new sequence.
- However at selected places, most notably when used as
- <literal><#list <replaceable>...</replaceable>></literal>
- directive parameter, they are working in lazy mode instead, that
- is, they won't built the a new sequence, just stream through the
- existing one and apply the filter or mapping element by element.
- [TODO: document and link documentation]</para>
+ they aren't like in Java for example, and also, unlike the
+ similar Java methods, these built-ins aren't lazy in general,
+ only in specific cases (see more <link
+ linkend="topic.filterLazyEval">here</link>). The main goal of
+ adding these built-ins was to allow conditionally skipping
+ elements in the <literal>list</literal> directive without nested
+ <literal>if</literal>-s that interfere with the <link
+ linkend="ref.directive.sep"><literal>sep</literal>
+ directive</link>, and the <link
+ linkend="ref_builtins_loop_var">loop variable built-ins</link>
+ (see examples <link
+ linkend="ref_list_skipping">here</link>).</para>
</listitem>
<listitem>
@@ -27937,23 +28728,17 @@
<literal><replaceable>seq</replaceable>?size</literal>,
<literal><replaceable>seq</replaceable>[<replaceable>index</replaceable>]</literal>,
<literal><replaceable>seq</replaceable>[<replaceable>range</replaceable>]</literal>,
- and with some built-ins (<literal>filter</literal>,
+ and with some other built-ins (<literal>filter</literal>,
<literal>map</literal>, <literal>join</literal>, etc.) to spare
collecting all the elements into the memory when possible. For
- example <literal>anIterator?sequence[1]</literal> now will just
- fetch the first 2 items, instead of building a sequence that
- contains all the elements, and then getting the 2nd element from
- that. Or, if you write
- <literal>anIterator?sequence?size</literal>, it will just skip
- through all elements to count them, but won't store them in
- memory. These optimizations only work within the same chain of
- built-in calls, so for example in <literal><#assign seq =
- anIterator?sequence>${seq[1]}</literal> will still collect
- all the elements into the memory, as
- <literal>anIterator?sequence</literal> and
- <literal>seq[1]</literal> are separated. [TODO: document this at
- <link linkend="ref_builtin_sequence">ref_builtin_sequence</link>
- too]</para>
+ example <literal>anIterator?sequence[1]</literal> will now just
+ fetch the first 2 items, while earlier it has built a sequence
+ that contains all the elements, only to get the 2nd element from
+ that. Or, <literal>anIterator?sequence?size</literal> will now
+ just count the elements, without collecting them into the
+ memory. See <link
+ linkend="ref_builtin_sequence_optimizations">the
+ reference</link> for more details.</para>
</listitem>
<listitem>
@@ -28002,14 +28787,14 @@
<literal>${<replaceable>aBoolean</replaceable>}</literal> will
behave as
<literal>${<replaceable>aBoolean</replaceable>?c}</literal>.
- This should be only used if you are generating output for
+ This should only be used if you are generating output for
non-human (computer) consumption only. If your output has pieces
for human audience too, it's still recommended to use
<literal>${<replaceable>aBoolean</replaceable>?c}</literal>
where <literal>true</literal>/<literal>false</literal> output is
needed, and either not set the <literal>boolean_format</literal>
at all, or set it to something that's appropriate for everyday
- uses (like <literal>"yes,no"</literal>).</para>
+ users (like <literal>"yes,no"</literal>).</para>
</listitem>
<listitem>
@@ -28032,16 +28817,23 @@
<listitem>
<para>If the result of
<literal><replaceable>seq</replaceable>?size</literal> is
- compared to an integer literal in a template, like in
- <literal><replaceable>seq</replaceable>?size != 0</literal>, or
- <literal><replaceable>seq</replaceable>?size < 1</literal>,
- and to decide the answer it's enough to know if
- <literal><replaceable>seq</replaceable></literal> is an empty
- sequence or collection (i.e., the exact size isn't needed), and
- said value implements
+ compared to an integer <emphasis>literal</emphasis> in a
+ template, like in <literal><replaceable>seq</replaceable>?size
+ != 0</literal>, or <literal><replaceable>seq</replaceable>?size
+ < 1</literal>, and to decide the answer it's enough to know
+ if <literal><replaceable>seq</replaceable></literal> is empty or
+ not (i.e., the exact size isn't needed), and
+ <literal><replaceable>seq</replaceable></literal> implements
<literal>TemplateCollectionModelEx</literal>, FreeMarker will
call <literal>TemplateCollectionModelEx.isEmpty()</literal>
- instead of <literal>size()</literal>.</para>
+ instead of <literal>size()</literal>. Furthermore, if
+ <literal><replaceable>seq</replaceable></literal> is the result
+ of <literal>?filter</literal>, or of a similar built-ins that
+ can provide lazily generated result, it will do counting to
+ figure out the size (rather than constructing the whole sequence
+ in memory), and will limit how far it counts based on what
+ literal the result of <literal>?size</literal> is compared
+ with.</para>
</listitem>
<listitem>
@@ -28049,8 +28841,8 @@
<literal>TemplateModelUtils.wrapAsHashUnion(ObjectWrapper,
List<?>)</literal> and
<literal>wrapAsHashUnion(ObjectWrapper, Object...)</literal>,
- which meant to be used when you want to compose a data-model
- from multiple objects in a way so that their entries
+ which is useful when you want to compose the data-model from
+ multiple objects in a way so that their entries
(<literal>Map</literal> key-value pairs, bean properties, etc.)
appear together on the top level of the data-model.</para>
</listitem>
diff --git a/src/test/java/freemarker/core/LambdaParsingTest.java b/src/test/java/freemarker/core/LambdaParsingTest.java
new file mode 100644
index 0000000..92df5d6
--- /dev/null
+++ b/src/test/java/freemarker/core/LambdaParsingTest.java
@@ -0,0 +1,33 @@
+/*
+ * 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 org.junit.Test;
+
+import freemarker.test.TemplateTest;
+
+public class LambdaParsingTest extends TemplateTest {
+
+ @Test
+ public void testPrecedence() throws Exception {
+ assertOutput("${[1, 2, 3]?filter(it -> it == 1 || it == 3)?join(', ')}", "1, 3");
+ }
+
+}
diff --git a/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java b/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java
index fbbf226..0dee5f4 100644
--- a/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java
+++ b/src/test/java/freemarker/core/LazilyGeneratedCollectionTest.java
@@ -265,6 +265,28 @@
assertOutput("${seqLong?filter(x->true)[2..*3]?size}", "[size][get 0][get 1][get 2][get 3][get 4]3");
}
+ @Test
+ public void testNonDirectCalledBuiltInsAreNotLazy() throws Exception {
+ assertOutput("" +
+ "<#assign changing = 1>" +
+ "<#assign method = [1, 2]?filter(it -> it != changing)?join>" +
+ "<#assign changing = 2>" +
+ "${method(', ')}",
+ "2");
+ assertOutput("" +
+ "<#assign changing = 1>" +
+ "<#assign method = [1, 2]?filter(it -> it != changing)?seq_contains>" +
+ "<#assign changing = 2>" +
+ "${method(2)?c}",
+ "true");
+ assertOutput("" +
+ "<#assign changing = 1>" +
+ "<#assign method = [1, 2]?filter(it -> it != changing)?seq_index_of>" +
+ "<#assign changing = 2>" +
+ "${method(2)}",
+ "0");
+ }
+
public static abstract class ListContainingTemplateModel {
protected final List<Number> elements;
diff --git a/src/test/java/freemarker/core/MapBiTest.java b/src/test/java/freemarker/core/MapBiTest.java
index 3e70c0f..927ea52 100644
--- a/src/test/java/freemarker/core/MapBiTest.java
+++ b/src/test/java/freemarker/core/MapBiTest.java
@@ -165,6 +165,11 @@
"<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
"${(1..3)?map(tenTimes)?seqIndexOf(20)} ${s}", "1 1;2;");
+ assertOutput("" +
+ "<#assign s = ''>" +
+ "<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
+ "${[1, 2, 3, 2, 5]?map(tenTimes)?seqLastIndexOf(20)} ${s}", "3 1;2;3;2;5;");
+
// For these this test can't check that there was no sequence built, but at least we know they are working:
assertOutput("${(1..3)?map(it -> it * 10)?min}", "10");
assertOutput("${(1..3)?map(it -> it * 10)?max}", "30");
diff --git a/src/test/resources/freemarker/test/templatesuite/templates/range-lazy.ftl b/src/test/resources/freemarker/test/templatesuite/templates/range-lazy.ftl
index 4fe96e1..02f5710 100644
--- a/src/test/resources/freemarker/test/templatesuite/templates/range-lazy.ftl
+++ b/src/test/resources/freemarker/test/templatesuite/templates/range-lazy.ftl
@@ -1,3 +1,22 @@
+<#--
+ 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.
+-->
+
<#assign s="012">
<#assign seq=[0, 1, 2]>