freemarker-notifications mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From ddek...@apache.org
Subject 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
Date Sun, 17 Sep 2017 16:28:40 GMT
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 <ddekany@apache.org>
Authored: Sun Sep 17 18:28:30 2017 +0200
Committer: ddekany <ddekany@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");
+    }
+
+}


Mime
View raw message