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 2017/09/17 16:28:40 UTC
incubator-freemarker git commit: New built-in,
sequence. This can be used to work around situations where a listable
value lacks some features that you need in the template (like it can't be
listed twice, it can't tell its size, etc.), and you can't modi
Repository: incubator-freemarker
Updated Branches:
refs/heads/2.3-gae a67111065 -> 2d0b49319
New built-in, sequence. This can be used to work around situations where a listable value lacks some features that you need in the template (like it can't be listed twice, it can't tell its size, etc.), and you can't modify the data-model to fix the problem. See more...
Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/2d0b4931
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/2d0b4931
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/2d0b4931
Branch: refs/heads/2.3-gae
Commit: 2d0b49319a216e610a5aa0ade5dd24ae7881457d
Parents: a671110
Author: ddekany <dd...@apache.org>
Authored: Sun Sep 17 18:28:30 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Sun Sep 17 18:28:30 2017 +0200
----------------------------------------------------------------------
src/main/java/freemarker/core/BuiltIn.java | 4 +-
.../freemarker/core/BuiltInsForSequences.java | 29 +++++++
.../freemarker/core/CollectionAndSequence.java | 11 ++-
.../freemarker/core/NonSequenceException.java | 9 ++-
.../core/UnexpectedTypeException.java | 12 +++
src/manual/en_US/book.xml | 84 ++++++++++++++++++--
.../freemarker/core/SequenceBuiltInTest.java | 82 +++++++++++++++++++
7 files changed, 216 insertions(+), 15 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2d0b4931/src/main/java/freemarker/core/BuiltIn.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltIn.java b/src/main/java/freemarker/core/BuiltIn.java
index b4e3239..630f895 100644
--- a/src/main/java/freemarker/core/BuiltIn.java
+++ b/src/main/java/freemarker/core/BuiltIn.java
@@ -61,6 +61,7 @@ import freemarker.core.BuiltInsForSequences.lastBI;
import freemarker.core.BuiltInsForSequences.reverseBI;
import freemarker.core.BuiltInsForSequences.seq_containsBI;
import freemarker.core.BuiltInsForSequences.seq_index_ofBI;
+import freemarker.core.BuiltInsForSequences.sequenceBI;
import freemarker.core.BuiltInsForSequences.sortBI;
import freemarker.core.BuiltInsForSequences.sort_byBI;
import freemarker.core.BuiltInsForStringsMisc.evalBI;
@@ -83,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 = 263;
+ static final int NUMBER_OF_BIS = 264;
static final HashMap<String, BuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
static {
@@ -262,6 +263,7 @@ abstract class BuiltIn extends Expression implements Cloneable {
putBI("seq_contains", "seqContains", new seq_containsBI());
putBI("seq_index_of", "seqIndexOf", new seq_index_ofBI(true));
putBI("seq_last_index_of", "seqLastIndexOf", new seq_index_ofBI(false));
+ putBI("sequence", new sequenceBI());
putBI("short", new shortBI());
putBI("size", new BuiltInsForMultipleTypes.sizeBI());
putBI("sort_by", "sortBy", new sort_byBI());
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2d0b4931/src/main/java/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/BuiltInsForSequences.java b/src/main/java/freemarker/core/BuiltInsForSequences.java
index 8430133..f2d1c6a 100644
--- a/src/main/java/freemarker/core/BuiltInsForSequences.java
+++ b/src/main/java/freemarker/core/BuiltInsForSequences.java
@@ -30,8 +30,10 @@ import java.util.List;
import freemarker.ext.beans.CollectionModel;
import freemarker.template.SimpleNumber;
import freemarker.template.SimpleScalar;
+import freemarker.template.SimpleSequence;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateCollectionModelEx;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
@@ -829,6 +831,33 @@ class BuiltInsForSequences {
}
+ static class sequenceBI extends BuiltIn {
+
+ @Override
+ TemplateModel _eval(Environment env) throws TemplateException {
+ TemplateModel model = target.eval(env);
+
+ if (model instanceof TemplateSequenceModel && !isBuggySeqButGoodCollection(model)) {
+ return model;
+ }
+
+ if (!(model instanceof TemplateCollectionModel)) {
+ throw new NonSequenceOrCollectionException(target, model, env);
+ }
+ TemplateCollectionModel coll = (TemplateCollectionModel) model;
+
+ SimpleSequence seq =
+ coll instanceof TemplateCollectionModelEx
+ ? new SimpleSequence(((TemplateCollectionModelEx) coll).size())
+ : new SimpleSequence();
+ for (TemplateModelIterator iter = coll.iterator(); iter.hasNext(); ) {
+ seq.add(iter.next());
+ }
+ return seq;
+ }
+
+ }
+
private static boolean isBuggySeqButGoodCollection(
TemplateModel model) {
return model instanceof CollectionModel
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2d0b4931/src/main/java/freemarker/core/CollectionAndSequence.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/CollectionAndSequence.java b/src/main/java/freemarker/core/CollectionAndSequence.java
index 79e7576..33556ff 100644
--- a/src/main/java/freemarker/core/CollectionAndSequence.java
+++ b/src/main/java/freemarker/core/CollectionAndSequence.java
@@ -23,6 +23,7 @@ import java.io.Serializable;
import java.util.ArrayList;
import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateCollectionModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;
@@ -30,13 +31,13 @@ import freemarker.template.TemplateSequenceModel;
/**
* Add sequence capabilities to an existing collection, or
- * vice versa. Used by ?keys and ?values built-ins.
+ * vice versa. Used by the ?keys and ?values built-ins.
*/
final public class CollectionAndSequence
implements TemplateCollectionModel, TemplateSequenceModel, Serializable {
private TemplateCollectionModel collection;
private TemplateSequenceModel sequence;
- private ArrayList data;
+ private ArrayList<TemplateModel> data;
public CollectionAndSequence(TemplateCollectionModel collection) {
this.collection = collection;
@@ -59,13 +60,15 @@ implements TemplateCollectionModel, TemplateSequenceModel, Serializable {
return sequence.get(i);
} else {
initSequence();
- return (TemplateModel) data.get(i);
+ return data.get(i);
}
}
public int size() throws TemplateModelException {
if (sequence != null) {
return sequence.size();
+ } if (collection instanceof TemplateCollectionModelEx) {
+ return ((TemplateCollectionModelEx) collection).size();
} else {
initSequence();
return data.size();
@@ -74,7 +77,7 @@ implements TemplateCollectionModel, TemplateSequenceModel, Serializable {
private void initSequence() throws TemplateModelException {
if (data == null) {
- data = new ArrayList();
+ data = new ArrayList<TemplateModel>();
TemplateModelIterator it = collection.iterator();
while (it.hasNext()) {
data.add(it.next());
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2d0b4931/src/main/java/freemarker/core/NonSequenceException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/NonSequenceException.java b/src/main/java/freemarker/core/NonSequenceException.java
index 2b85ab9..21440bc 100644
--- a/src/main/java/freemarker/core/NonSequenceException.java
+++ b/src/main/java/freemarker/core/NonSequenceException.java
@@ -21,6 +21,7 @@ package freemarker.core;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateSequenceModel;
+import freemarker.template.utility.CollectionUtils;
/**
* Indicates that a {@link TemplateSequenceModel} value was expected, but the value had a different type.
@@ -46,19 +47,19 @@ public class NonSequenceException extends UnexpectedTypeException {
NonSequenceException(
Expression blamed, TemplateModel model, Environment env)
throws InvalidReferenceException {
- super(blamed, model, "sequence", EXPECTED_TYPES, env);
+ this(blamed, model, CollectionUtils.EMPTY_OBJECT_ARRAY, env);
}
NonSequenceException(
Expression blamed, TemplateModel model, String tip,
Environment env)
throws InvalidReferenceException {
- super(blamed, model, "sequence", EXPECTED_TYPES, tip, env);
+ this(blamed, model, new Object[] { tip }, env);
}
NonSequenceException(
- Expression blamed, TemplateModel model, String[] tips, Environment env) throws InvalidReferenceException {
+ Expression blamed, TemplateModel model, Object[] tips, Environment env) throws InvalidReferenceException {
super(blamed, model, "sequence", EXPECTED_TYPES, tips, env);
}
-
+
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2d0b4931/src/main/java/freemarker/core/UnexpectedTypeException.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/UnexpectedTypeException.java b/src/main/java/freemarker/core/UnexpectedTypeException.java
index 0878f81..61b0fe4 100644
--- a/src/main/java/freemarker/core/UnexpectedTypeException.java
+++ b/src/main/java/freemarker/core/UnexpectedTypeException.java
@@ -19,8 +19,13 @@
package freemarker.core;
+import java.util.Arrays;
+
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateCollectionModelEx;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
+import freemarker.template.TemplateSequenceModel;
/**
* The type of a value differs from what was expected.
@@ -88,6 +93,13 @@ public class UnexpectedTypeException extends TemplateException {
errorDescBuilder.tip(tip);
}
}
+ if (model instanceof TemplateCollectionModel
+ && (Arrays.asList(expectedTypes).contains(TemplateSequenceModel.class)
+ || Arrays.asList(expectedTypes).contains(TemplateCollectionModelEx.class))) {
+ errorDescBuilder.tip("As the problematic value contains a collection of items, you could convert it "
+ + "to a sequence like someValue?sequence. Be sure though that you won't have a large number of "
+ + "items, as all will be held in memory one the same time.");
+ }
return errorDescBuilder;
}
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2d0b4931/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 21c3479..569cbac 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -12812,6 +12812,10 @@ grant codeBase "file:/path/to/freemarker.jar"
</listitem>
<listitem>
+ <para><link linkend="ref_builtin_sequence">sequence</link></para>
+ </listitem>
+
+ <listitem>
<para><link linkend="ref_builtin_sort_by">sort_by</link></para>
</listitem>
@@ -18439,6 +18443,61 @@ ${1305575275540?number_to_time}</programlisting>
May 16, 2011
3:47:55 PM</programlisting>
</section>
+
+ <section xml:id="ref_builtin_sequence">
+ <title>sequence</title>
+
+ <indexterm>
+ <primary>seq_sequence built-in</primary>
+ </indexterm>
+
+ <para>This built-in is used to convert a listable value (one that
+ you can iterate through with the <link
+ linkend="ref.directive.list"><literal>list</literal>
+ directive</link>) to a more capable <link
+ linkend="dgui_datamodel_container">sequence</link> value. Sequences
+ 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
+ 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
+ non-<literal>List</literal> <literal>java.util.Collection</literal>,
+ or a <literal>java.util.Iterator</literal>).</para>
+
+ <para>If the value is already a sequence, then this built-in just
+ 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>
+
+ <para>You should convert a value with <literal>sequence</literal>
+ only once. If you need the resulting sequence at multiple places,
+ always assign the result to a variable, because if the value you
+ convert is only listable once, converting it for the second time
+ will result in error or an empty sequence. Also the conversion is
+ somewhat costly for big collections, so it's better to do it only
+ once.</para>
+
+ <para>Example: Let's say you find that <literal>users</literal> is
+ only listable once (because it's a
+ <literal>java.util.Iterator</literal>), but you need to list it for
+ multiple times in the template, and you can't fix the data-model.
+ Then you could do this:</para>
+
+ <programlisting role="template"><#-- Collect all the users into a sequence: -->
+<#assign usersSeq = users?sequence>
+
+<#list usersSeq as user>...</#list>
+Again:
+<#list usersSeq as user>...</#list>
+</programlisting>
+ </section>
</section>
</chapter>
@@ -18494,6 +18553,11 @@ May 16, 2011
</listitem>
<listitem>
+ <para><link
+ linkend="ref.directive.list.continue">continue</link></para>
+ </listitem>
+
+ <listitem>
<para><link linkend="ref.directive.default">default</link></para>
</listitem>
@@ -26989,11 +27053,10 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
<itemizedlist>
<listitem>
- <para>Added the <link
- linkend="ref.directive.list.continue"><literal>continue</literal>
- directive</link>, which can be used inside a
- <literal>list</literal> to skip to the next iteration (similarly
- as in Java).</para>
+ <para>New directive: <literal>continue</literal>. This can be
+ used inside the <literal>list</literal> directive to skip to the
+ next iteration (similarly as in Java). <link
+ linkend="ref.directive.list.continue">See more...</link></para>
</listitem>
<listitem>
@@ -27010,6 +27073,15 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
</listitem>
<listitem>
+ <para>New built-in, <literal>sequence</literal>. This can be
+ used to work around situations where a listable value lacks some
+ features that you need in the template (like it can't be listed
+ twice, it can't tell its size, etc.), and you can't modify the
+ data-model to fix the problem. <link
+ linkend="ref_builtin_sequence">See more...</link></para>
+ </listitem>
+
+ <listitem>
<para>Bug fixed (<link
xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-70">FREEMARKER-70</link>):
The usage of loop variable built-ins, like
@@ -27033,7 +27105,7 @@ TemplateModel x = env.getVariable("x"); // get variable x</programlisting>
xlink:href="https://issues.apache.org/jira/browse/FREEMARKER-71">FREEMARKER-71</link>):
When using
<literal><replaceable>exp</replaceable>?eval</literal>, if the
- expression inside evaluated string throws an exception, the
+ expression inside the evaluated string throws an exception, the
cause exception of that exception was lost.</para>
</listitem>
</itemizedlist>
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/2d0b4931/src/test/java/freemarker/core/SequenceBuiltInTest.java
----------------------------------------------------------------------
diff --git a/src/test/java/freemarker/core/SequenceBuiltInTest.java b/src/test/java/freemarker/core/SequenceBuiltInTest.java
new file mode 100644
index 0000000..77eecc5
--- /dev/null
+++ b/src/test/java/freemarker/core/SequenceBuiltInTest.java
@@ -0,0 +1,82 @@
+package freemarker.core;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableSet;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultIterableAdapter;
+import freemarker.template.DefaultNonListCollectionAdapter;
+import freemarker.template.TemplateCollectionModelEx;
+import freemarker.template.TemplateException;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateSequenceModel;
+import freemarker.template.utility.ObjectWrapperWithAPISupport;
+import freemarker.test.TemplateTest;
+
+public class SequenceBuiltInTest extends TemplateTest {
+
+ @Test
+ public void testWithCollection() throws TemplateException, IOException {
+ ObjectWrapperWithAPISupport ow = (ObjectWrapperWithAPISupport) getConfiguration().getObjectWrapper();
+
+ TemplateModel xs = DefaultIterableAdapter.adapt(ImmutableSet.of("a", "b"), ow);
+ assertThat(xs, not(instanceOf(TemplateCollectionModelEx.class)));
+ assertThat(xs, not(instanceOf(TemplateSequenceModel.class)));
+ addToDataModel("xs", xs);
+
+ try {
+ assertOutput("${xs[1]}", "b");
+ fail();
+ } catch (TemplateException e) {
+ System.out.println(e); //!!T
+ assertThat(e.getMessage(), containsString("?sequence")); // Contains tip to use ?sequence
+ }
+ assertOutput("${xs?sequence[1]}", "b");
+
+ try {
+ assertOutput("${xs?size}", "2");
+ fail();
+ } catch (TemplateException e) {
+ System.out.println(e); //!!T
+ assertThat(e.getMessage(), containsString("?sequence")); // Contains tip to use ?sequence
+ }
+ assertOutput("${xs?sequence?size}", "2");
+ }
+
+ @Test
+ public void testWithCollectionEx() throws TemplateException, IOException {
+ ObjectWrapperWithAPISupport ow = (ObjectWrapperWithAPISupport) getConfiguration().getObjectWrapper();
+
+ TemplateModel xs = DefaultNonListCollectionAdapter.adapt(ImmutableSet.of("a", "b"), ow);
+ assertThat(xs, not(instanceOf(TemplateSequenceModel.class)));
+ assertThat(xs, instanceOf(TemplateCollectionModelEx.class));
+ addToDataModel("xs", xs);
+
+ try {
+ assertOutput("${xs[1]}", "b");
+ fail();
+ } catch (TemplateException e) {
+ assertThat(e.getMessage(), containsString("?sequence")); // Contains tip to use ?sequence
+ }
+ assertOutput("${xs?sequence[1]}", "b");
+
+ assertOutput("${xs?size}", "2"); // No need for ?sequence
+ }
+
+ @Test
+ public void testWithSequence() throws TemplateException, IOException {
+ assertOutput("${[11, 12]?sequence[1]}", "12");
+
+
+ getConfiguration().setIncompatibleImprovements(Configuration.VERSION_2_3_23);
+ // As it returns the sequence as is, it works with an infinite sequence:
+ assertOutput("${(11..)?sequence[1]}", "12");
+ }
+
+}