Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 aec8e6672 -> cece2e1cb


?keys and ?values now return a collection model (not a sequence model), 
similarly as Java's Map keys()/Values do return a Collection. To help handling 
the rare cases where one still wants them as sequences, added `?sequence`. It's 
the responsibility of the user to decide if the iterable value can contain too 
many items for that. (In FM2 such conversion could be triggered without the 
user realizing it, in the case of ?keys/?values return values. Now it's at 
least done explicitly.)


Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo
Commit: 
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/cece2e1c
Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/cece2e1c
Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/cece2e1c

Branch: refs/heads/3
Commit: cece2e1cbc102127aecd3d41b50cf9089da3baa9
Parents: aec8e66
Author: ddekany <[email protected]>
Authored: Tue Sep 5 13:14:31 2017 +0200
Committer: ddekany <[email protected]>
Committed: Tue Sep 5 13:14:31 2017 +0200

----------------------------------------------------------------------
 FM3-CHANGE-LOG.txt                              | 12 +++
 .../freemarker/core/SequenceBuiltInTest.java    | 75 ++++++++++++++++++
 .../core/cano-identifier-escaping.ftl           |  2 +-
 .../core/cano-identifier-escaping.ftl.out       |  2 +-
 .../expected/identifier-escaping.txt            |  2 +-
 .../core/templatesuite/expected/macros.txt      |  6 +-
 .../templatesuite/templates/hashliteral.ftl     |  4 +-
 .../templates/identifier-escaping.ftl           |  4 +-
 .../core/templatesuite/templates/macros.ftl     |  2 +-
 .../apache/freemarker/core/ASTExpBuiltIn.java   |  3 +-
 .../freemarker/core/ASTExpHashLiteral.java      |  5 +-
 .../freemarker/core/BuiltInForSequence.java     |  3 +
 .../freemarker/core/BuiltInsForHashes.java      | 12 ++-
 .../freemarker/core/BuiltInsForSequences.java   | 47 +++++++----
 .../apache/freemarker/core/MessageUtils.java    | 10 +++
 .../apache/freemarker/core/NativeSequence.java  |  4 +
 .../freemarker/core/model/impl/BeanModel.java   |  4 +-
 .../core/model/impl/IterableAndSequence.java    | 82 --------------------
 18 files changed, 160 insertions(+), 119 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index 5e6ace9..e1bbb44 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -131,17 +131,29 @@ Node: Changes already mentioned above aren't repeated 
here!
   - It's not tolerated anymore if the caller of a macro has declared more 
nested content parameters than
     what `#nested` passes to it (like in `<#macro m><#nested 1, 2></#macro> 
<@m ; i, j, k>...</@>`, where `k` has
     no corresponding value in `#nested`).
+    (The template converter tool can't do this conversion.)
   - In `#macro` (and `function`) named parameters with default values need not 
be at the end of the parameter list
     anymore. (Positional parameters with default values need to be at the end 
of the list of positional parameters
     only.)
   - When parameter default expressions are evaluated, only the parameters 
defined earlier are already set.
     So `<#macro m a b=a>` works, but `<#macro m a=b b>` won't work anymore 
(unless there's `b` outside the
     parameter list), as `b` is not yet set when the default of `a` is 
calculated.
+    (The template converter tool can't do this conversion.)
 - Built-ins don't convert their parameters to string anymore; in FM some (not 
all) of them did, because they
   were based on the TemplateMethod interface (deprecated even in FM2) that 
required String paramteters and
   was auto-converted by FM2. (The template converter tool can't do this 
conversion.)
 - The string ranges like `someString[1..0]` (that is, where the end is one 
less than the beginning) aren't
   tolerated anymore, and are errors. (The template converter tool can't do 
this conversion.)
+- Added `?sequence`, which converts an iterable value into a sequence. This is 
to be used with care, as iterables
+  that are not also sequences sometimes contain a large number of items, and 
the resulting sequence will have to
+  store all of them.
+- `?keys` and `?values` (used to return the keys and values in a hash) now 
returns a collection, not a sequence.
+  Note that for FM3 collections ?size works, but not accessing by index. This 
is to be more aligned with the
+  capabilities of java.util.Map. In case you know that you won't have a too 
large number of entries, and you need
+  a sequence of the keys or values, use `myMap?keys?sequence` or 
`myMap?values?sequence`.
+  (The template converter tool can't do this conversion. Because `?sequence` 
is a relatively expensive operation,
+  it shouldn't be invoked for multiple times on the same value, instead the 
result should be extracted into a
+  variable, like: `<#assign keySeq = keys?sequence>[... Do multiple things 
with keySeq here ...]`.
 
   
 Java API changes

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java
 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java
new file mode 100644
index 0000000..0809d9e
--- /dev/null
+++ 
b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java
@@ -0,0 +1,75 @@
+/*
+ * 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 org.apache.freemarker.core;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.impl.SimpleCollection;
+import org.apache.freemarker.core.model.impl.SimpleIterable;
+import org.apache.freemarker.test.TemplateTest;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+
+public class SequenceBuiltInTest extends TemplateTest {
+
+    @Test
+    public void testWithIterable() throws TemplateException, IOException {
+        TemplateModel xs = new SimpleIterable(ImmutableList.of("a", "b"), 
getConfiguration().getObjectWrapper());
+        assertThat(xs, not(instanceOf(TemplateCollectionModel.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");
+    }
+
+    @Test
+    public void testWithCollection() throws TemplateException, IOException {
+        TemplateModel xs = new SimpleCollection(ImmutableList.of("a", "b"), 
getConfiguration().getObjectWrapper());
+        assertThat(xs, not(instanceOf(TemplateSequenceModel.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");
+    }
+
+    @Test
+    public void testWithSequence() throws TemplateException, IOException {
+        // As it returns the sequence as is, it works with an infinite 
sequence:
+        assertOutput("${(11..)?sequence[1]}", "12");
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl
index 41923cc..c866a06 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl
@@ -69,7 +69,7 @@ ${.vars['as"d']}
 <#global g\-a=1 g\-b=2 "g-c"=3>
 
 <#macro dumpNS>
-    <#list .namespace?keys?sort as k>
+    <#list .namespace?keys?sequence?sort as k>
         ${k} = <#local v = .namespace[k]><#if 
v?isString>${v}<#else>...</#if><#lt>
     </#list>
 </#macro>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out
index 05dc62d..f50dedb 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out
@@ -40,5 +40,5 @@ ${hash["--moz-prop"]}
 <#assign "as'c" = "as4">${.vars["as'c"]}
 <#assign 'as"d' = "as5">${.vars['as"d']}
 
-<#global g\-a = 1, g\-b = 2, g\-c = 3><#macro dumpNS><#list 
.namespace?keys?sort as k>${k} = <#local v = .namespace[k]><#if 
v?isString>${v}<#else>...</#if>
+<#global g\-a = 1, g\-b = 2, g\-c = 3><#macro dumpNS><#list 
.namespace?keys?sequence?sort as k>${k} = <#local v = .namespace[k]><#if 
v?isString>${v}<#else>...</#if>
 </#list></#macro><@dumpNS/>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt
index 1c62bd5..6f7ba21 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt
@@ -38,7 +38,7 @@ as3
 as4
 as5
 
-<catchAll x=1 y=2 a:b.c=5 data-foo=4 z=3 />
+<catchAll x=1 y=2 z=3 data-foo=4 a:b.c=5 />
 
 ---.: = dash-dash-dash etc.
 @as@_a = as1

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt
index 680a08e..11d860f 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt
@@ -56,9 +56,9 @@
 <p>Test "catch-all" macro parameter:</p>
 
 
-foo=a baz=[]
-foo=a baz=[bar=b]
-foo=a baz=[bar=b, baz=c]
+foo=a bar=[]
+foo=a bar=[bar=b]
+foo=a bar=[bar=b, baz=c]
 
 
 Hello World! Today is Monday.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl
index dc3c972..463e41b 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl
@@ -80,8 +80,8 @@ ${test["1"]}
 
 <p>Hash concatenation:</p>
 <#assign cc = { "a" : 1, "b" : 2 } + { "b" : 3, "c" : 4 }>
-<#list cc?keys?sort as key>
-${key} => ${cc[key]}
+<#list cc as key, value>
+${key} => ${value}
 </#list>
 
 <p>Empty hash concatenation:</p>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl
index b221deb..1d472a7 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl
@@ -69,12 +69,12 @@ ${.vars['as"d']}
 <#global g\-a=1 g\-b=2 "g-c"=3>
 
 <#macro catchAll x y attrs...>
-<catchAll x=${x} y=${y}<#list attrs?keys?sort as k> ${k}=${attrs[k]}</#list> />
+<catchAll x=${x} y=${y}<#list attrs as k, v> ${k}=${v}</#list> />
 </#macro>
 <@catchAll x=1 y=2 z=3 data\-foo=4 a\:b\.c=5 />
 
 <#macro dumpNS>
-    <#list .namespace?keys?sort as k>
+    <#list .namespace?keys?sequence?sort as k>
         ${k} = <#local v = .namespace[k]><#if 
v?isString>${v}<#else>...</#if><#lt>
     </#list>
 </#macro>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl
----------------------------------------------------------------------
diff --git 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl
 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl
index 181326c..7ef6395 100644
--- 
a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl
+++ 
b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl
@@ -75,7 +75,7 @@
 <p>Test "catch-all" macro parameter:</p>
 
 <#macro catch\-all foo bar...>
-foo=${foo} baz=[<#list bar?keys?sort as key>${key}=${bar[key]}<#if 
key_has_next>, </#if></#list>]
+foo=${foo} bar=[<#list bar as k, v>${k}=${v}<#sep>, </#list>]
 </#macro>
 <#assign catchall = .namespace["catch-all"]>
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
index b0aa3b0..322d662 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java
@@ -75,7 +75,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements 
Cloneable {
     protected ASTExpression target;
     protected String key;
 
-    static final int NUMBER_OF_BIS = 262;
+    static final int NUMBER_OF_BIS = 263;
     static final HashMap<String, ASTExpBuiltIn> BUILT_INS_BY_NAME = new 
HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f);
 
     static {
@@ -247,6 +247,7 @@ abstract class ASTExpBuiltIn extends ASTExpression 
implements Cloneable {
         putBI("removeEnding", new BuiltInsForStringsBasic.remove_endingBI());
         putBI("removeBeginning", new 
BuiltInsForStringsBasic.remove_beginningBI());
         putBI("rtf", new BuiltInsForStringsEncoding.rtfBI());
+        putBI("sequence", new BuiltInsForSequences.sequenceBI());
         putBI("seqContains", new seq_containsBI());
         putBI("seqIndexOf", new seq_index_ofBI(true));
         putBI("seqLastIndexOf", new seq_index_ofBI(false));

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
index dd29369..7198f6a 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
@@ -28,7 +28,6 @@ import 
org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.impl.IterableAndSequence;
 
 /**
  * AST expression node: <tt>{ keyExp: valueExp, ... }</tt> 
@@ -130,7 +129,7 @@ final class ASTExpHashLiteral extends ASTExpression {
         @Override
         public TemplateCollectionModel keys() {
             if (keyCollection == null) {
-                keyCollection = new IterableAndSequence(new 
NativeStringCollectionCollection(map.keySet()));
+                keyCollection = new 
NativeStringCollectionCollection(map.keySet());
             }
             return keyCollection;
         }
@@ -138,7 +137,7 @@ final class ASTExpHashLiteral extends ASTExpression {
         @Override
         public TemplateCollectionModel values() {
             if (valueCollection == null) {
-                valueCollection = new IterableAndSequence(new 
NativeCollection(map.values()));
+                valueCollection = new NativeCollection(map.values());
             }
             return valueCollection;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java
index cb7cc73..8306b8f 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java
@@ -22,6 +22,9 @@ package org.apache.freemarker.core;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 
+/**
+ * Built-in that meant to acts on {@link TemplateSequenceModel}-s only.
+ */
 abstract class BuiltInForSequence extends ASTExpBuiltIn {
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java
index 4d2a9d5..eaf12f5 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java
@@ -19,11 +19,9 @@
 
 package org.apache.freemarker.core;
 
-import org.apache.freemarker.core.model.TemplateIterableModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.IterableAndSequence;
 
 /**
  * A holder for builtins that operate exclusively on hash left-hand value.
@@ -35,9 +33,9 @@ class BuiltInsForHashes {
         @Override
         TemplateModel calculateResult(TemplateHashModelEx hashExModel, 
Environment env)
                 throws TemplateException, InvalidReferenceException {
-            TemplateIterableModel keys = hashExModel.keys();
+            TemplateCollectionModel keys = hashExModel.keys();
             if (keys == null) throw newNullPropertyException("keys", 
hashExModel, env);
-            return keys instanceof TemplateSequenceModel ? keys : new 
IterableAndSequence(keys);
+            return keys;
         }
         
     }
@@ -46,9 +44,9 @@ class BuiltInsForHashes {
         @Override
         TemplateModel calculateResult(TemplateHashModelEx hashExModel, 
Environment env)
                 throws TemplateException, InvalidReferenceException {
-            TemplateIterableModel values = hashExModel.values();
+            TemplateCollectionModel values = hashExModel.values();
             if (values == null) throw newNullPropertyException("values", 
hashExModel, env);
-            return values instanceof TemplateSequenceModel ? values : new 
IterableAndSequence(values);
+            return values;
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
index c28b385..6b8be3d 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java
@@ -31,6 +31,7 @@ import java.util.Date;
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateDateModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
@@ -51,11 +52,12 @@ import org.apache.freemarker.core.util._StringUtils;
  * A holder for builtins that operate on sequence (or some even on iterable) 
left-hand value.
  */
 class BuiltInsForSequences {
-    
+
+    // TODO [FM3] Should work on TemplateIterableModel as well
     static class chunkBI extends BuiltInForSequence {
 
         private class BIMethod extends BuiltInCallableImpl implements 
TemplateFunctionModel {
-            
+
             private final TemplateSequenceModel tsm;
 
             private BIMethod(TemplateSequenceModel tsm) {
@@ -79,15 +81,15 @@ class BuiltInsForSequences {
         }
 
         private static class ChunkedSequence implements TemplateSequenceModel {
-            
+
             private final TemplateSequenceModel wrappedTsm;
-            
+
             private final int chunkSize;
-            
+
             private final TemplateModel fillerItem;
-            
+
             private final int numberOfChunks;
-            
+
             private ChunkedSequence(
                     TemplateSequenceModel wrappedTsm, int chunkSize, 
TemplateModel fillerItem)
                     throws TemplateException {
@@ -103,9 +105,9 @@ class BuiltInsForSequences {
                 if (chunkIndex >= numberOfChunks || chunkIndex < 0) {
                     return null;
                 }
-                
+
                 return new TemplateSequenceModel() {
-                    
+
                     private final int baseIndex = chunkIndex * chunkSize;
 
                     @Override
@@ -155,16 +157,16 @@ class BuiltInsForSequences {
                 return new SequenceTemplateModelIterator(this);
             }
         }
-        
+
         @Override
         TemplateModel calculateResult(TemplateSequenceModel tsm) throws 
TemplateException {
             return new BIMethod(tsm);
         }
-        
+
     }
-    
+
     static class firstBI extends BuiltInForIterable {
-        
+
         @Override
         TemplateModel calculateResult(TemplateIterableModel model) throws 
TemplateException {
             TemplateModelIterator iter = model.iterator();
@@ -814,6 +816,25 @@ class BuiltInsForSequences {
         }
     }
 
+    static class sequenceBI extends BuiltInForIterable {
+
+        @Override
+        TemplateModel calculateResult(TemplateIterableModel model) throws 
TemplateException {
+            if (model instanceof TemplateSequenceModel) {
+                return model;
+            }
+            NativeSequence seq =
+                    model instanceof TemplateCollectionModel
+                            ? new NativeSequence(((TemplateCollectionModel) 
model).getCollectionSize())
+                            : new NativeSequence();
+            for (TemplateModelIterator iter = model.iterator(); 
iter.hasNext(); ) {
+                seq.add(iter.next());
+            }
+            return seq;
+        }
+
+    }
+
     // Can't be instantiated
     private BuiltInsForSequences() { }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
index 7b9952e..515b59b 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java
@@ -19,11 +19,15 @@
 
 package org.apache.freemarker.core;
 
+import java.util.Arrays;
+
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateDateModel;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateNumberModel;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util.TemplateLanguageUtils;
@@ -344,6 +348,12 @@ class MessageUtils {
                 errorDescBuilder.tip(tip);
             }
         }
+        if (model instanceof TemplateIterableModel && 
Arrays.asList(expectedTypes).contains(TemplateSequenceModel
+                .class)) {
+            errorDescBuilder.tip("As the problematic value contains a 
collection of items, so you could covert it "
+                    + "to a sequence like someValue?sequence. Be sure that you 
won't have a large number of items "
+                    + "though, as all will be held in memory at once.");
+        }
         return errorDescBuilder;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java 
b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java
index b5febb8..9f7af3a 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java
@@ -38,6 +38,10 @@ class NativeSequence implements TemplateSequenceModel, 
Serializable {
 
     private final ArrayList<TemplateModel> items;
 
+    public NativeSequence() {
+        items = new ArrayList<>();
+    }
+
     public NativeSequence(int capacity) {
         items = new ArrayList<>(capacity);
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
index 3f4c45b..accadaf 100644
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
+++ 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
@@ -289,7 +289,7 @@ public class BeanModel
 
     @Override
     public TemplateCollectionModel keys() {
-        return new 
IterableAndSequence(DefaultNonListCollectionAdapter.adapt(keySet(), wrapper));
+        return DefaultNonListCollectionAdapter.adapt(keySet(), wrapper);
     }
 
     @Override
@@ -300,7 +300,7 @@ public class BeanModel
             String key = ((TemplateStringModel) it.next()).getAsString();
             values.add(get(key));
         }
-        return new 
IterableAndSequence(DefaultNonListCollectionAdapter.adapt(values, wrapper));
+        return DefaultNonListCollectionAdapter.adapt(values, wrapper);
     }
     
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java
----------------------------------------------------------------------
diff --git 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java
 
b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java
deleted file mode 100644
index eb51098..0000000
--- 
a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * 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 org.apache.freemarker.core.model.impl;
-
-import java.util.ArrayList;
-
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateIterableModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-
-/**
- * Add sequence capabilities to an existing iterable. Used by ?keys and 
?values built-ins.
- */
-// TODO [FM3] Maybe ?keys/?values should just return a TemplateCollectionModel
-final public class IterableAndSequence implements TemplateSequenceModel {
-    private TemplateIterableModel iterable;
-    private ArrayList<TemplateModel> data;
-
-    public IterableAndSequence(TemplateIterableModel iterable) {
-        this.iterable = iterable;
-    }
-
-    @Override
-    public TemplateModelIterator iterator() throws TemplateException {
-        return iterable.iterator();
-    }
-
-    @Override
-    public TemplateModel get(int i) throws TemplateException {
-        initSequence();
-        return i < data.size() && i >= 0 ? data.get(i) : null;
-    }
-
-    @Override
-    public int getCollectionSize() throws TemplateException {
-        if (iterable instanceof TemplateCollectionModel) {
-            return ((TemplateCollectionModel) iterable).getCollectionSize();
-        } else {
-            initSequence();
-            return data.size();
-        }
-    }
-
-    @Override
-    public boolean isEmptyCollection() throws TemplateException {
-        if (iterable instanceof TemplateCollectionModel) {
-            return ((TemplateCollectionModel) iterable).isEmptyCollection();
-        } else {
-            return iterable.iterator().hasNext();
-        }
-    }
-
-    private void initSequence() throws TemplateException {
-        if (data == null) {
-            data = new ArrayList<>();
-            TemplateModelIterator it = iterable.iterator();
-            while (it.hasNext()) {
-                data.add(it.next());
-            }
-        }
-    }
-}

Reply via email to