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">&lt;#-- Collect all the users into a sequence: --&gt;
+&lt;#assign usersSeq = users?sequence&gt;
+
+&lt;#list usersSeq as user&gt;...&lt;/#list&gt;
+Again:
+&lt;#list usersSeq as user&gt;...&lt;/#list&gt;
+</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");
+    }
+
+}