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/01 18:58:24 UTC

[5/6] incubator-freemarker git commit: Reworked list/iterable-like TemplateModel interfaced. Now we have TemplateIterableModel (which is like TemplateCollectionModel in FM2), TemplateCollectionModel (which similar to TemplateCollectionModelEx in FM2) tha

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl
index 18c035d..738dc76 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl
@@ -16,7 +16,308 @@
   specific language governing permissions and limitations
   under the License.
 -->
-<#include 'range-common.ftl'>
+<#-- A version of "?join" that fails at null-s in the sequence: -->
+<#function join(seq, sep='')>
+  <#local r = "">
+  <#list seq as i>
+    <#local r = r + i>
+    <#if i_has_next>
+      <#local r = r + sep>
+    </#if>
+  </#list>
+  <#return r>
+</#function>
+
+<#----------------------->
+<#-- Range expressions -->
+
+<@assertEquals actual=join(1..2, ' ') expected="1 2" />
+<@assertEquals actual=join(1..1, ' ') expected="1" />
+<@assertEquals actual=join(1..0, ' ') expected="1 0" />
+<@assertEquals actual=join(1..-1, ' ') expected="1 0 -1" />
+<@assertEquals actual=join(-1..-1, ' ') expected="-1" />
+<@assertEquals actual=join(-1..1, ' ') expected="-1 0 1" />
+
+<@assertEquals actual=join(1..<3, ' ') expected="1 2" />
+<@assertEquals actual=join(1..<2, ' ') expected="1" />
+<@assertEquals actual=join(1..<1, ' ') expected="" />
+<@assertEquals actual=join(1..<0, ' ') expected="1" />
+<@assertEquals actual=join(1..<-1, ' ') expected="1 0" />
+<@assertEquals actual=join(1..<-2, ' ') expected="1 0 -1" />
+<@assertEquals actual=join(-1..<0, ' ') expected="-1" />
+<@assertEquals actual=join(-1..<2, ' ') expected="-1 0 1" />
+
+<@assertEquals actual=join(1..!3, ' ') expected="1 2" />
+<@assertEquals actual=join(1..!2, ' ') expected="1" />
+<@assertEquals actual=join(1..!1, ' ') expected="" />
+<@assertEquals actual=join(1..!0, ' ') expected="1" />
+<@assertEquals actual=join(1..!-1, ' ') expected="1 0" />
+<@assertEquals actual=join(1..!-2, ' ') expected="1 0 -1" />
+<@assertEquals actual=join(-1..!0, ' ') expected="-1" />
+<@assertEquals actual=join(-1..!2, ' ') expected="-1 0 1" />
+
+<@assertEquals actual=join(1..*2, ' ') expected="1 2" />
+<@assertEquals actual=join(1..*1, ' ') expected="1" />
+<@assertEquals actual=join(1..*0, ' ') expected="" />
+<@assertEquals actual=join(1..*-1, ' ') expected="1" />
+<@assertEquals actual=join(1..*-2, ' ') expected="1 0" />
+<@assertEquals actual=join(1..*-3, ' ') expected="1 0 -1" />
+<@assertEquals actual=join(-1..*1, ' ') expected="-1" />
+<@assertEquals actual=join(-1..*3, ' ') expected="-1 0 1" />
+
+<@assertEquals actual=1 expected=(0..0)?size />
+<@assertEquals actual=1 expected=(1..1)?size />
+<@assertEquals actual=1 expected=(2..2)?size />
+<@assertEquals actual=2 expected=(0..1)?size />
+<@assertEquals actual=2 expected=(1..2)?size />
+<@assertEquals actual=2 expected=(2..3)?size />
+<@assertEquals actual=3 expected=(2..4)?size />
+<@assertEquals actual=2 expected=(1..0)?size />
+<@assertEquals actual=2 expected=(2..1)?size />
+<@assertEquals actual=2 expected=(3..2)?size />
+<@assertEquals actual=3 expected=(4..2)?size />
+
+<@assertEquals actual=0 expected=(0..<0)?size />
+<@assertEquals actual=0 expected=(1..<1)?size />
+<@assertEquals actual=0 expected=(2..<2)?size />
+<@assertEquals actual=1 expected=(0..<1)?size />
+<@assertEquals actual=1 expected=(1..<2)?size />
+<@assertEquals actual=1 expected=(2..<3)?size />
+<@assertEquals actual=2 expected=(2..<4)?size />
+<@assertEquals actual=1 expected=(1..<0)?size />
+<@assertEquals actual=1 expected=(2..<1)?size />
+<@assertEquals actual=1 expected=(3..<2)?size />
+<@assertEquals actual=2 expected=(4..<2)?size />
+
+<@assertEquals actual=0 expected=(0..*0)?size />
+<@assertEquals actual=0 expected=(1..*0)?size />
+<@assertEquals actual=0 expected=(2..*0)?size />
+<@assertEquals actual=1 expected=(0..*1)?size />
+<@assertEquals actual=1 expected=(1..*1)?size />
+<@assertEquals actual=1 expected=(2..*1)?size />
+<@assertEquals actual=2 expected=(2..*2)?size />
+<@assertEquals actual=1 expected=(0..*-1)?size />
+<@assertEquals actual=1 expected=(1..*-1)?size />
+<@assertEquals actual=1 expected=(2..*-1)?size />
+<@assertEquals actual=2 expected=(0..*-2)?size />
+<@assertEquals actual=2 expected=(1..*-2)?size />
+<@assertEquals actual=2 expected=(2..*-2)?size />
+
+
+<#--------------------->
+<#-- String slicing: -->
+
+<#assign s = 'abcd'>
+
+<@assertEquals actual=s[0..] expected="abcd" />
+<@assertEquals actual=s[1..] expected="bcd" />
+<@assertEquals actual=s[2..] expected="cd" />
+<@assertEquals actual=s[3..] expected="d" />
+<@assertEquals actual=s[4..] expected="" />
+<@assertFails message="5 is out of bounds">
+  <#assign _ = s[5..] />
+</...@assertFails>
+<@assertFails message="6 is out of bounds">
+  <#assign _ = s[6..] />
+</...@assertFails>
+
+<@assertEquals actual=s[1..2] expected="bc" />
+<@assertEquals actual=s[1..1] expected="b" />
+<@assertEquals actual=s[0..1] expected="ab" />
+<@assertEquals actual=s[0..0] expected="a" />
+<@assertFails message="4 is out of bounds">
+  <#assign _ = s[1..4] />
+</...@assertFails>
+<@assertFails message="5 is out of bounds">
+  <#assign _ = s[1..5] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[-1..1] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[-2..1] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[0..-1] />
+</...@assertFails>
+
+<@assertEquals actual=s[1..<3] expected="bc" />
+<@assertEquals actual=s[1..!3] expected="bc" />
+<@assertEquals actual=s[1..<2] expected="b" />
+<@assertEquals actual=s[1..<0] expected="b" />
+<@assertEquals actual=s[1..<1] expected="" />
+<@assertEquals actual=s[0..<0] expected="" />
+<@assertEquals actual=s[5..<5] expected="" />
+<@assertEquals actual=s[6..<6] expected="" />
+<@assertEquals actual=s[-5..<-5] expected="" />
+<@assertFails message="negative">
+  <#assign _ = s[-5..<1] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[2..<-4] />
+</...@assertFails>
+<@assertFails message="decreasing">
+  <#assign _ = s[2..<0] />
+</...@assertFails>
+
+<@assertEquals actual=s[1..*-1] expected="b" />
+<@assertEquals actual=s[1..*0] expected="" />
+<@assertEquals actual=s[1..*1] expected="b" />
+<@assertEquals actual=s[1..*2] expected="bc" />
+<@assertEquals actual=s[1..*3] expected="bcd" />
+<@assertEquals actual=s[1..*4] expected="bcd" />
+<@assertEquals actual=s[1..*5] expected="bcd" />
+<@assertEquals actual=s[4..*1] expected="" />
+<@assertEquals actual=s[5..*0] expected="" />
+<@assertEquals actual=s[6..*0] expected="" />
+<@assertEquals actual=s[-5..*0] expected="" />
+<@assertEquals actual=s[0..*0] expected="" />
+<@assertEquals actual=s[0..*-1] expected="a" />
+<@assertEquals actual=s[0..*-2] expected="a" />
+<@assertEquals actual=s[0..*-3] expected="a" />
+<@assertFails message="5 is out of bounds">
+  <#assign _ = s[5..*1] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[-1..*1] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[-2..*1] />
+</...@assertFails>
+<@assertFails message="decreasing">
+  <#assign _ = s[1..*-2] />
+</...@assertFails>
+<@assertFails message="decreasing">
+  <#assign _ = s[1..*-3] />
+</...@assertFails>
+<@assertFails message="4 is out of bounds">
+  <#assign _ = s[4..*-1] />
+</...@assertFails>
+
+<#-- FreeMarker 2 string backward-range bug not supported anymore: -->
+<@assertFails message="decreasing">
+  <#assign _ = s[1..0] />
+</...@assertFails>
+<@assertFails message="decreasing">
+  <#assign _ = s[2..1] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[0..-1] />
+</...@assertFails>
+<@assertFails message="decreasing">
+  <#assign _ = s[3..1] />
+</...@assertFails>
+<#-- The bug was never emulated for operators introduced after 2.3.20: -->
+<@assertFails message="decreasing">
+  <#assign _ = s[3..<1] />
+</...@assertFails>
+<@assertFails message="decreasing">
+  <#assign _ = s[3..*-2] />
+</...@assertFails>
+
+<#assign r = 1..2>
+<@assertEquals actual=s[r] expected="bc" />
+<#assign r = 2..1>
+<@assertFails message="decreasing">
+  <#assign _ = s[r] />
+</...@assertFails>
+<#assign r = 1..<2>
+<@assertEquals actual=s[r] expected="b" />
+<#assign r = 2..<4>
+<@assertEquals actual=s[r] expected="cd" />
+<#assign r = 2..>
+<@assertEquals actual=s[r] expected="cd" />
+<#assign r = 1..*2>
+<@assertEquals actual=s[r] expected="bc" />
+
+<#----------------------->
+<#-- Sequence slicing: -->
+
+<#assign s = ['a', 'b', 'c', 'd']>
+
+<@assertEquals actual=join(s[0..]) expected="abcd" />
+<@assertEquals actual=join(s[1..]) expected="bcd" />
+<@assertEquals actual=join(s[2..]) expected="cd" />
+<@assertEquals actual=join(s[3..]) expected="d" />
+<@assertEquals actual=join(s[4..]) expected="" />
+<@assertFails message="5 is out of bounds">
+  <#assign _ = s[5..] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[-1..] />
+</...@assertFails>
+
+<@assertEquals actual=join(s[1..2]) expected="bc" />
+<@assertEquals actual=join(s[1..1]) expected="b" />
+<@assertEquals actual=join(s[0..1]) expected="ab" />
+<@assertEquals actual=join(s[0..0]) expected="a" />
+<@assertFails message="5 is out of bounds">
+  <#assign _ = s[1..5] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[-1..0] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[0..-1] />
+</...@assertFails>
+
+<@assertEquals actual=join(s[1..<3]) expected="bc" />
+<@assertEquals actual=join(s[1..!3]) expected="bc" />
+<@assertEquals actual=join(s[1..<2]) expected="b" />
+<@assertEquals actual=join(s[1..<0]) expected="b" />
+<@assertEquals actual=join(s[1..<1]) expected="" />
+<@assertEquals actual=join(s[0..<0]) expected="" />
+
+<@assertEquals actual=join(s[1..0]) expected="ba" />
+<@assertEquals actual=join(s[2..1]) expected="cb" />
+<@assertEquals actual=join(s[2..0]) expected="cba" />
+<@assertEquals actual=join(s[2..<0]) expected="cb" />
+<@assertEquals actual=join(s[1..<0]) expected="b" />
+<@assertEquals actual=join(s[0..<0]) expected="" />
+<@assertEquals actual=join(s[3..<1]) expected="dc" />
+<@assertEquals actual=join(s[2..<1]) expected="c" />
+<@assertEquals actual=join(s[1..<1]) expected="" />
+<@assertEquals actual=join(s[0..<1]) expected="a" />
+<@assertEquals actual=join(s[0..<0]) expected="" />
+<@assertEquals actual=join(s[5..<5]) expected="" />
+<@assertEquals actual=join(s[-5..<-5]) expected="" />
+
+<@assertEquals actual=join(s[0..*-4]) expected="a" />
+<@assertEquals actual=join(s[1..*-4]) expected="ba" />
+<@assertEquals actual=join(s[1..*-3]) expected="ba" />
+<@assertEquals actual=join(s[1..*-2]) expected="ba" />
+<@assertEquals actual=join(s[1..*-1]) expected="b" />
+<@assertEquals actual=join(s[1..*0]) expected="" />
+<@assertEquals actual=join(s[1..*1]) expected="b" />
+<@assertEquals actual=join(s[1..*2]) expected="bc" />
+<@assertEquals actual=join(s[1..*3]) expected="bcd" />
+<@assertEquals actual=join(s[1..*4]) expected="bcd" />
+<@assertEquals actual=join(s[1..*5]) expected="bcd" />
+<@assertEquals actual=join(s[0..*3]) expected="abc" />
+<@assertEquals actual=join(s[2..*3]) expected="cd" />
+<@assertEquals actual=join(s[3..*3]) expected="d" />
+<@assertEquals actual=join(s[4..*3]) expected="" />
+<@assertFails message="5 is out of bounds">
+  <#assign _ = s[5..*3] />
+</...@assertFails>
+<@assertFails message="negative">
+  <#assign _ = s[-1..*2] />
+</...@assertFails>
+
+<#assign r = 1..2>
+<@assertEquals actual=join(s[r]) expected="bc" />
+<#assign r = 2..0>
+<@assertEquals actual=join(s[r]) expected="cba" />
+<#assign r = 1..<2>
+<@assertEquals actual=join(s[r]) expected="b" />
+<#assign r = 2..<0>
+<@assertEquals actual=join(s[r]) expected="cb" />
+<#assign r = 2..>
+<@assertEquals actual=join(s[r]) expected="cd" />
+<#assign r = 1..*2>
+<@assertEquals actual=join(s[r]) expected="bc" />
+<#assign r = 1..*-9>
+<@assertEquals actual=join(s[r]) expected="ba" />
 
 <@assertEquals actual=(4..)?size expected=2147483647 />
 <@assertEquals actual=limitedJoin(4.., 3) expected="4, 5, 6, ..." />
@@ -24,9 +325,8 @@
 <@assertEquals actual=(4..)[0] expected=4 />
 <@assertEquals actual=(4..)[1] expected=5 />
 <@assertEquals actual=(4..)[1000000] expected=1000004 />
-<@assertFails message="out of bounds">
-	<@assertEquals actual=(4..)[-1] expected=5 />
-</@>
+<@assert !(4..)[-1]?? />
+<@assert !(1..2)[2]?? />
 
 <#assign r = 2147483646..>
 <@assertEquals actual=r?size expected=2147483647 />

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl
index 3442824..489bd2f 100644
--- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl
+++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl
@@ -17,12 +17,12 @@
   under the License.
 -->
 <#setting booleanFormat="1,0">
-StNuBoHaHxSeCoCxEnInFuDiNo
+StNuBoHaHxItCoSeFuDiNo
 <#list [
   "a", 1, false,
-  testfunction, testmacro,
-  {"a":1}, [1], testcollection, testcollectionEx,
-  testnode,
+  testFunction, testMacro,
+  {"a":1}, [1], testIterable, testCollection,
+  testNode,
   bean, bean.m, bean.mOverloaded
 ] as x>
   ${x?isString} <#t>
@@ -30,13 +30,11 @@ StNuBoHaHxSeCoCxEnInFuDiNo
   ${x?isBoolean} <#t>
   ${x?isHash} <#t>
   ${x?isHashEx} <#t>
-  ${x?isSequence} <#t>
+  ${x?isIterable} <#t>
   ${x?isCollection} <#t>
-  ${x?isCollectionEx} <#t>
-  ${x?isEnumerable} <#t>
-  ${x?isIndexable} <#t>
+  ${x?isSequence} <#t>
   ${x?isFunction} <#t>
   ${x?isDirective} <#t>
   ${x?isNode}<#lt>
 </#list>
-<#macro testmacro></#macro>
\ No newline at end of file
+<#macro testMacro></#macro>
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
index b795ca1..1d0927f 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
@@ -25,15 +25,14 @@ import java.util.Collection;
 import java.util.Collections;
 
 import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePair;
 import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateStringModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.util._StringUtils;
 
@@ -50,7 +49,7 @@ final class ASTDirList extends ASTDirective {
 
     /**
      * @param listedExp
-     *            a variable referring to a sequence or collection or extended hash to list
+     *            a variable referring to an iterable or extended hash that we want to list
      * @param nestedContentParamName
      *            The name of the variable that will hold the value of the current item when looping through listed value,
      *            or {@code null} if we have a nested {@code #items}. If this is a hash listing then this variable will holds the value
@@ -252,15 +251,15 @@ final class ASTDirList extends ASTDirective {
         private boolean executeNestedContent(Environment env, ASTElement[] childBuffer)
                 throws TemplateException, IOException {
             return !hashListing
-                    ? executedNestedContentForCollOrSeqListing(env, childBuffer)
+                    ? executedNestedContentForIterableListing(env, childBuffer)
                     : executedNestedContentForHashListing(env, childBuffer);
         }
 
-        private boolean executedNestedContentForCollOrSeqListing(Environment env, ASTElement[] childBuffer)
+        private boolean executedNestedContentForIterableListing(Environment env, ASTElement[] childBuffer)
                 throws IOException, TemplateException {
             final boolean listNotEmpty;
-            if (listedValue instanceof TemplateCollectionModel) {
-                final TemplateCollectionModel collModel = (TemplateCollectionModel) listedValue;
+            if (listedValue instanceof TemplateIterableModel) {
+                final TemplateIterableModel collModel = (TemplateIterableModel) listedValue;
                 final TemplateModelIterator iterModel
                         = openedIterator == null ? collModel.iterator()
                                 : ((TemplateModelIterator) openedIterator);
@@ -279,31 +278,12 @@ final class ASTDirList extends ASTDirective {
                         }
                         openedIterator = null;
                     } else {
-                        // We must reuse this later, because TemplateCollectionModel-s that wrap an Iterator only
+                        // We must reuse this later, because TemplateIterableModel-s that wrap an Iterator only
                         // allow one iterator() call.
                         openedIterator = iterModel;
                         env.visit(childBuffer);
                     }
                 }
-            } else if (listedValue instanceof TemplateSequenceModel) {
-                final TemplateSequenceModel seqModel = (TemplateSequenceModel) listedValue;
-                final int size = seqModel.size();
-                listNotEmpty = size != 0;
-                if (listNotEmpty) {
-                    if (nestedContentParam1Name != null) {
-                        try {
-                            for (index = 0; index < size; index++) {
-                                nestedContentParam = seqModel.get(index);
-                                hasNext = (size > index + 1);
-                                env.visit(childBuffer);
-                            }
-                        } catch (ASTDirBreak.Break br) {
-                            // Silently exit loop
-                        }
-                    } else {
-                        env.visit(childBuffer);
-                    }
-                }
             } else if (listedValue instanceof TemplateHashModelEx) {
                 throw new TemplateException(env,
                         new _ErrorDescriptionBuilder("The value you try to list is ",
@@ -314,8 +294,8 @@ final class ASTDirList extends ASTDirective {
             } else {
                 throw MessageUtils.newUnexpectedOperandTypeException(
                         listedExp, listedValue,
-                        MessageUtils.SEQUENCE_OR_COLLECTION,
-                        MessageUtils.EXPECTED_TYPES_SEQUENCE_OR_COLLECTION,
+                        MessageUtils.EXPECTED_TYPE_ITERABLE_DESC,
+                        TemplateIterableModel.class,
                         null, env);
             }
             return listNotEmpty;
@@ -390,8 +370,7 @@ final class ASTDirList extends ASTDirective {
                         }
                     }
                 }
-            } else if (listedValue instanceof TemplateCollectionModel
-                    || listedValue instanceof TemplateSequenceModel) {
+            } else if (listedValue instanceof TemplateIterableModel) {
                 throw new TemplateException(env,
                         new _ErrorDescriptionBuilder("The value you try to list is ",
                                 new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(listedValue)),

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
index 645aff4..786bfcf 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java
@@ -26,6 +26,7 @@ import java.util.List;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.util.StringToIndexMap;
 import org.apache.freemarker.core.util._StringUtils;
 
@@ -290,7 +291,7 @@ final class ASTDirMacroOrFunction extends ASTDirective implements TemplateModel
         public Collection<String> getLocalVariableNames() throws TemplateException {
             HashSet<String> result = new HashSet<>();
             for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) {
-                result.add(it.next().toString());
+                result.add(((TemplateStringModel) it.next()).getAsString());
             }
             return result;
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
index a849aaf..25344e6 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java
@@ -45,10 +45,10 @@ final class ASTDirNested extends ASTDirective {
     ASTElement[] accept(Environment env) throws IOException, TemplateException {
         CallPlace macroCallPlace = env.getCurrentMacroContext().callPlace;
 
-        // When nestedContParamCnt < nestedContentParameters.size(), then we just skip calculating the extra parameters,
-        // and CallPlace.executeNestedContent will be successful. Note sure if this lenient behavior is a good idea,
-        // but for now it's inherited from FM2, so TODO [FM3].
-        // When nestedContParamCnt > nestedContentParameters.size(), then later
+        // When nestedContParamCnt < nestedContentParameters.getCollectionSize(), then we just skip calculating the
+        // extra parameters, and CallPlace.executeNestedContent will be successful. Note sure if this lenient
+        // behavior is a good idea, but for now it's inherited from FM2, so TODO [FM3].
+        // When nestedContParamCnt > nestedContentParameters.getCollectionSize(), then later
         // CallPlace.executeNestedContent will throw exception, but we let that happen so that the error message
         // generation remains centralized. (In FM2 not even this was an error.)
         TemplateModel[] nestedContParamValues;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
index a076d7e..5fa8eac 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
@@ -23,16 +23,15 @@ import java.util.HashSet;
 import java.util.Set;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
+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.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateNumberModel;
-import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.model.impl.CollectionAndSequence;
+import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl.SimpleString;
 
@@ -122,9 +121,9 @@ final class ASTExpAddOrConcat extends ASTExpression {
         if (leftModel instanceof TemplateHashModelEx && rightModel instanceof TemplateHashModelEx) {
             TemplateHashModelEx leftModelEx = (TemplateHashModelEx) leftModel;
             TemplateHashModelEx rightModelEx = (TemplateHashModelEx) rightModel;
-            if (leftModelEx.size() == 0) {
+            if (leftModelEx.getHashSize() == 0) {
                 return rightModelEx;
-            } else if (rightModelEx.size() == 0) {
+            } else if (rightModelEx.getHashSize() == 0) {
                 return leftModelEx;
             } else {
                 return new ConcatenatedHashEx(leftModelEx, rightModelEx);
@@ -179,9 +178,7 @@ final class ASTExpAddOrConcat extends ASTExpression {
         return ParameterRole.forBinaryOperatorOperand(idx);
     }
 
-    private static final class ConcatenatedSequence
-    implements
-        TemplateSequenceModel {
+    private static final class ConcatenatedSequence implements TemplateSequenceModel {
         private final TemplateSequenceModel left;
         private final TemplateSequenceModel right;
 
@@ -191,21 +188,63 @@ final class ASTExpAddOrConcat extends ASTExpression {
         }
 
         @Override
-        public int size()
+        public int getCollectionSize()
         throws TemplateException {
-            return left.size() + right.size();
+            return left.getCollectionSize() + right.getCollectionSize();
         }
 
         @Override
-        public TemplateModel get(int i)
-        throws TemplateException {
-            int ls = left.size();
-            return i < ls ? left.get(i) : right.get(i - ls);
+        public boolean isEmptyCollection() throws TemplateException {
+            return left.isEmptyCollection() && right.isEmptyCollection();
+        }
+
+        @Override
+        public TemplateModel get(int i) throws TemplateException {
+            int leftSize = left.getCollectionSize();
+            return i < leftSize ? left.get(i) : right.get(i - leftSize);
+        }
+
+        @Override
+        public TemplateModelIterator iterator() throws TemplateException {
+            return new ConcatenatedTemplateModelIterator(left.iterator(), right.iterator());
         }
     }
 
-    private static class ConcatenatedHash
-    implements TemplateHashModel {
+    private static class ConcatenatedTemplateModelIterator implements  TemplateModelIterator {
+        private final TemplateModelIterator left;
+        private final TemplateModelIterator right;
+        private boolean leftExhausted;
+
+        ConcatenatedTemplateModelIterator(TemplateModelIterator left, TemplateModelIterator right) {
+            this.left = left;
+            this.right = right;
+        }
+
+        @Override
+        public TemplateModel next() throws TemplateException {
+            if (!leftExhausted) {
+                if (left.hasNext()) {
+                    return left.next();
+                }
+                leftExhausted = true;
+            }
+            return right.next();
+        }
+
+        @Override
+        public boolean hasNext() throws TemplateException {
+            if (!leftExhausted) {
+                if (left.hasNext()) {
+                    return true;
+                }
+                leftExhausted = true;
+            }
+            // At this point leftExhausted is true.
+            return right.hasNext();
+        }
+    }
+
+    private static class ConcatenatedHash implements TemplateHashModel {
         protected final TemplateHashModel left;
         protected final TemplateHashModel right;
 
@@ -222,17 +261,16 @@ final class ASTExpAddOrConcat extends ASTExpression {
         }
 
         @Override
-        public boolean isEmpty()
+        public boolean isEmptyHash()
         throws TemplateException {
-            return left.isEmpty() && right.isEmpty();
+            return left.isEmptyHash() && right.isEmptyHash();
         }
     }
 
-    private static final class ConcatenatedHashEx
-    extends ConcatenatedHash
+    private static final class ConcatenatedHashEx extends ConcatenatedHash
     implements TemplateHashModelEx {
-        private CollectionAndSequence keys;
-        private CollectionAndSequence values;
+        private TemplateSequenceModel keys;
+        private TemplateSequenceModel values;
         private int size;
 
         ConcatenatedHashEx(TemplateHashModelEx left, TemplateHashModelEx right) {
@@ -240,34 +278,33 @@ final class ASTExpAddOrConcat extends ASTExpression {
         }
         
         @Override
-        public int size() throws TemplateException {
+        public int getHashSize() throws TemplateException {
             initKeys();
             return size;
         }
 
         @Override
-        public TemplateCollectionModel keys()
+        public TemplateIterableModel keys()
         throws TemplateException {
             initKeys();
             return keys;
         }
 
         @Override
-        public TemplateCollectionModel values()
+        public TemplateIterableModel values()
         throws TemplateException {
             initValues();
             return values;
         }
 
-        private void initKeys()
-        throws TemplateException {
+        private void initKeys() throws TemplateException {
             if (keys == null) {
                 HashSet keySet = new HashSet();
                 NativeSequence keySeq = new NativeSequence(32);
                 addKeys(keySet, keySeq, (TemplateHashModelEx) left);
                 addKeys(keySet, keySeq, (TemplateHashModelEx) right);
                 size = keySet.size();
-                keys = new CollectionAndSequence(keySeq);
+                keys = keySeq;
             }
         }
 
@@ -277,24 +314,23 @@ final class ASTExpAddOrConcat extends ASTExpression {
             while (it.hasNext()) {
                 TemplateStringModel tsm = (TemplateStringModel) it.next();
                 if (set.add(tsm.getAsString())) {
-                    // The first occurence of the key decides the index;
-                    // this is consisten with stuff like java.util.LinkedHashSet.
+                    // The first occurrence of the key decides the index;
+                    // this is consistent with stuff like java.util.LinkedHashSet.
                     keySeq.add(tsm);
                 }
             }
         }        
 
-        private void initValues()
-        throws TemplateException {
+        private void initValues() throws TemplateException {
             if (values == null) {
-                NativeSequence seq = new NativeSequence(size());
-                // Note: size() invokes initKeys() if needed.
+                NativeSequence seq = new NativeSequence(getHashSize());
+                // Note: getCollectionSize() invokes initKeys() if needed.
             
-                int ln = keys.size();
+                int ln = keys.getCollectionSize();
                 for (int i  = 0; i < ln; i++) {
                     seq.add(get(((TemplateStringModel) keys.get(i)).getAsString()));
                 }
-                values = new CollectionAndSequence(seq);
+                values = seq;
             }
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 8e542ba..b0aa3b0 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
@@ -121,8 +121,8 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable {
         putBI("int", new intBI());
         putBI("interpret", new BuiltInsForStringsMisc.interpretBI());
         putBI("isBoolean", new BuiltInsForMultipleTypes.is_booleanBI());
+        putBI("isIterable", new BuiltInsForMultipleTypes.is_iterableBI());
         putBI("isCollection", new BuiltInsForMultipleTypes.is_collectionBI());
-        putBI("isCollectionEx", new BuiltInsForMultipleTypes.is_collection_exBI());
         is_dateLikeBI bi = new BuiltInsForMultipleTypes.is_dateLikeBI();
         putBI("isDate", bi);  // misnomer
         putBI("isDateLike", bi);
@@ -133,11 +133,9 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable {
         putBI("isUnknownDateLike", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.UNKNOWN));
         putBI("isDatetime", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.DATE_TIME));
         putBI("isDirective", new BuiltInsForMultipleTypes.is_directiveBI());
-        putBI("isEnumerable", new BuiltInsForMultipleTypes.is_enumerableBI());
         putBI("isHashEx", new BuiltInsForMultipleTypes.is_hash_exBI());
         putBI("isHash", new BuiltInsForMultipleTypes.is_hashBI());
         putBI("isInfinite", new is_infiniteBI());
-        putBI("isIndexable", new BuiltInsForMultipleTypes.is_indexableBI());
         putBI("isMarkupOutput", new BuiltInsForMultipleTypes.is_markup_outputBI());
         putBI("isFunction", new BuiltInsForMultipleTypes.is_functionBI());
         putBI("isNan", new is_nanBI());
@@ -250,8 +248,8 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable {
         putBI("removeBeginning", new BuiltInsForStringsBasic.remove_beginningBI());
         putBI("rtf", new BuiltInsForStringsEncoding.rtfBI());
         putBI("seqContains", new seq_containsBI());
-        putBI("seqIndexOf", new seq_index_ofBI(1));
-        putBI("seqLastIndexOf", new seq_index_ofBI(-1));
+        putBI("seqIndexOf", new seq_index_ofBI(true));
+        putBI("seqLastIndexOf", new seq_index_ofBI(false));
         putBI("short", new shortBI());
         putBI("size", new BuiltInsForMultipleTypes.sizeBI());
         putBI("sortBy", new sort_byBI());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
index 6ca8b9e..4570948 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java
@@ -239,7 +239,7 @@ final class ASTExpBuiltInVariable extends ASTExpression {
         }
         
         @Override
-        public boolean isEmpty() {
+        public boolean isEmptyHash() {
             return false;
         }
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
index 6e892c9..07e12fb 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
@@ -20,49 +20,75 @@
 package org.apache.freemarker.core;
 
 
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateHashModelEx2;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateStringModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.TemplateStringModel;
 
 /** {@code exp!defExp}, {@code (exp)!defExp} and the same two with {@code (exp)!}. */
 class ASTExpDefault extends ASTExpression {
-	
-	static private class EmptyStringAndSequence
-	  implements TemplateStringModel, TemplateSequenceModel, TemplateHashModelEx {
-		@Override
+
+    static private class EmptyStringAndSequenceAndHash implements TemplateStringModel, TemplateSequenceModel,
+            TemplateHashModelEx2 {
+        @Override
         public String getAsString() {
-			return "";
-		}
-		@Override
+            return "";
+        }
+
+        @Override
         public TemplateModel get(int i) {
-			return null;
-		}
-		@Override
+            return null;
+        }
+
+        @Override
+        public TemplateModelIterator iterator() {
+            return TemplateModelIterator.EMPTY_ITERATOR;
+        }
+
+        @Override
         public TemplateModel get(String s) {
-			return null;
-		}
-		@Override
-        public int size() {
-			return 0;
-		}
-		@Override
-        public boolean isEmpty() {
-			return true;
-		}
-		@Override
-        public TemplateCollectionModel keys() {
-			return TemplateCollectionModel.EMPTY_COLLECTION;
-		}
-		@Override
-        public TemplateCollectionModel values() {
-			return TemplateCollectionModel.EMPTY_COLLECTION;
-		}
-		
-	}
-	
-	static final TemplateModel EMPTY_STRING_AND_SEQUENCE = new EmptyStringAndSequence();
+            return null;
+        }
+
+        @Override
+        public int getCollectionSize() {
+            return 0;
+        }
+
+        @Override
+        public boolean isEmptyCollection() throws TemplateException {
+            return true;
+        }
+
+        @Override
+        public int getHashSize() throws TemplateException {
+            return 0;
+        }
+
+        @Override
+        public boolean isEmptyHash() {
+            return true;
+        }
+
+        @Override
+        public TemplateIterableModel keys() {
+            return TemplateIterableModel.EMPTY_ITERABLE;
+        }
+
+        @Override
+        public TemplateIterableModel values() {
+            return TemplateIterableModel.EMPTY_ITERABLE;
+        }
+
+        @Override
+        public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+            return KeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR;
+        }
+    }
+
+    static final TemplateModel EMPTY_STRING_AND_SEQUENCE_AND_HASH = new EmptyStringAndSequenceAndHash();
 	
 	private final ASTExpression lho, rho;
 	
@@ -88,7 +114,7 @@ class ASTExpDefault extends ASTExpression {
 		}
 		
 		if (left != null) return left;
-		else if (rho == null) return EMPTY_STRING_AND_SEQUENCE;
+		else if (rho == null) return EMPTY_STRING_AND_SEQUENCE_AND_HASH;
 		else return rho.eval(env);
 	}
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
index 63e2ea2..e14e009 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java
@@ -78,14 +78,7 @@ final class ASTExpDynamicKeyName extends ASTExpression {
                                                Environment env)
         throws TemplateException {
         if (targetModel instanceof TemplateSequenceModel) {
-            TemplateSequenceModel tsm = (TemplateSequenceModel) targetModel;
-            int size;
-            try {
-                size = tsm.size();
-            } catch (Exception e) {
-                size = Integer.MAX_VALUE;
-            }
-            return index < size ? tsm.get(index) : null;
+            return ((TemplateSequenceModel) targetModel).get(index);
         }
 
         String s;
@@ -147,7 +140,7 @@ final class ASTExpDynamicKeyName extends ASTExpression {
             }
         }
         
-        final int size = range.size();
+        final int size = range.getCollectionSize();
         final boolean rightUnbounded = range.isRightUnbounded();
         final boolean rightAdaptive = range.isRightAdaptive();
         
@@ -157,14 +150,14 @@ final class ASTExpDynamicKeyName extends ASTExpression {
             return emptyResult(targetSeq != null);
         }
 
-        final int firstIdx = range.getBegining();
+        final int firstIdx = range.getBeginning();
         if (firstIdx < 0) {
             throw new TemplateException(keyExpression,
                     "Negative range start index (", Integer.valueOf(firstIdx),
                     ") isn't allowed for a range used for slicing.");
         }
         
-        final int targetSize = targetStr != null ? targetStr.length() : targetSeq.size();
+        final int targetSize = targetStr != null ? targetStr.length() : targetSeq.getCollectionSize();
         final int step = range.getStep();
         
         // Right-adaptive increasing ranges can start 1 after the last element of the target, because they are like
@@ -221,23 +214,13 @@ final class ASTExpDynamicKeyName extends ASTExpression {
             // List items are already wrapped, so the wrapper will be null:
             return resultSeq;
         } else {
-            final int exclEndIdx;
             if (step < 0 && resultSize > 1) {
-                if (!(range.isAffactedByStringSlicingBug() && resultSize == 2)) {
-                    throw new TemplateException(keyExpression,
-                            "Decreasing ranges aren't allowed for slicing strings (as it would give reversed text). "
-                            + "The index range was: first = ", Integer.valueOf(firstIdx),
-                            ", last = ", Integer.valueOf(firstIdx + (resultSize - 1) * step));
-                } else {
-                    // Emulate the legacy bug, where "foo"[n .. n-1] gives "" instead of an error (if n >= 1).  
-                    // Fix this in FTL [2.4]
-                    exclEndIdx = firstIdx;
-                }
-            } else {
-                exclEndIdx = firstIdx + resultSize;
+                throw new TemplateException(keyExpression,
+                        "Decreasing ranges aren't allowed for slicing strings (as it would give reversed text). "
+                        + "The index range was: first = ", Integer.valueOf(firstIdx),
+                        ", last = ", Integer.valueOf(firstIdx + (resultSize - 1) * step));
             }
-            
-            return new SimpleString(targetStr.substring(firstIdx, exclEndIdx));
+            return new SimpleString(targetStr.substring(firstIdx, firstIdx + resultSize));
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 f188631..a6eaab4 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
@@ -24,11 +24,11 @@ import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.ListIterator;
 
-import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 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.CollectionAndSequence;
+import org.apache.freemarker.core.model.impl.IterableAndSequence;
 
 /**
  * AST expression node: <tt>{ keyExp: valueExp, ... }</tt> 
@@ -108,7 +108,7 @@ final class ASTExpHashLiteral extends ASTExpression {
     private class LinkedHash implements TemplateHashModelEx2 {
 
         private HashMap<String, TemplateModel> map;
-        private TemplateCollectionModel keyCollection, valueCollection; // ordered lists of keys and values
+        private TemplateIterableModel keyCollection, valueCollection; // ordered lists of keys and values
 
         LinkedHash(Environment env) throws TemplateException {
             map = new LinkedHashMap<>();
@@ -123,22 +123,22 @@ final class ASTExpHashLiteral extends ASTExpression {
         }
 
         @Override
-        public int size() {
+        public int getHashSize() {
             return size;
         }
 
         @Override
-        public TemplateCollectionModel keys() {
+        public TemplateIterableModel keys() {
             if (keyCollection == null) {
-                keyCollection = new CollectionAndSequence(new NativeStringCollectionCollectionEx(map.keySet()));
+                keyCollection = new IterableAndSequence(new NativeStringCollectionCollection(map.keySet()));
             }
             return keyCollection;
         }
 
         @Override
-        public TemplateCollectionModel values() {
+        public TemplateIterableModel values() {
             if (valueCollection == null) {
-                valueCollection = new CollectionAndSequence(new NativeCollectionEx(map.values()));
+                valueCollection = new IterableAndSequence(new NativeCollection(map.values()));
             }
             return valueCollection;
         }
@@ -149,7 +149,7 @@ final class ASTExpHashLiteral extends ASTExpression {
         }
 
         @Override
-        public boolean isEmpty() {
+        public boolean isEmptyHash() {
             return size == 0;
         }
         

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
index 91d101a..1d3f8d9 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java
@@ -21,11 +21,8 @@ package org.apache.freemarker.core;
 
 import java.io.IOException;
 import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
 import java.util.ListIterator;
 
-import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 
@@ -34,9 +31,9 @@ import org.apache.freemarker.core.model.TemplateSequenceModel;
  */
 final class ASTExpListLiteral extends ASTExpression {
 
-    final ArrayList/*<ASTExpression>*/ items;
+    final ArrayList<ASTExpression> items;
 
-    ASTExpListLiteral(ArrayList items) {
+    ASTExpListLiteral(ArrayList<ASTExpression> items) {
         this.items = items;
         items.trimToSize();
     }
@@ -53,30 +50,6 @@ final class ASTExpListLiteral extends ASTExpression {
         return list;
     }
 
-    /**
-     * For {@link TemplateFunctionModel} calls, returns the list of arguments as {@link TemplateModel}-s.
-     */
-    // TODO [FM3][CF] This will be removed
-    List<TemplateModel> getModelList(Environment env) throws TemplateException {
-        int size = items.size();
-        switch(size) {
-            case 0: {
-                return Collections.emptyList();
-            }
-            case 1: {
-                return Collections.singletonList(((ASTExpression) items.get(0)).eval(env));
-            }
-            default: {
-                List result = new ArrayList(items.size());
-                for (ListIterator iterator = items.listIterator(); iterator.hasNext(); ) {
-                    ASTExpression exp = (ASTExpression) iterator.next();
-                    result.add(exp.eval(env));
-                }
-                return result;
-            }
-        }
-    }
-
     public int size() {
         return items.size();
     }
@@ -86,7 +59,7 @@ final class ASTExpListLiteral extends ASTExpression {
         StringBuilder buf = new StringBuilder("[");
         int size = items.size();
         for (int i = 0; i < size; i++) {
-            ASTExpression value = (ASTExpression) items.get(i);
+            ASTExpression value = items.get(i);
             buf.append(value.getCanonicalForm());
             if (i != size - 1) {
                 buf.append(", ");
@@ -107,7 +80,7 @@ final class ASTExpListLiteral extends ASTExpression {
             return true;
         }
         for (int i = 0; i < items.size(); i++) {
-            ASTExpression exp = (ASTExpression) items.get(i);
+            ASTExpression exp = items.get(i);
             if (!exp.isLiteral()) {
                 return false;
             }
@@ -118,7 +91,7 @@ final class ASTExpListLiteral extends ASTExpression {
     // A hacky routine used by ASTDirVisit and ASTDirRecurse
     TemplateSequenceModel evaluateStringsToNamespaces(Environment env) throws TemplateException {
         TemplateSequenceModel val = (TemplateSequenceModel) eval(env);
-        NativeSequence result = new NativeSequence(val.size());
+        NativeSequence result = new NativeSequence(val.getCollectionSize());
         for (int i = 0; i < items.size(); i++) {
             Object itemExpr = items.get(i);
             if (itemExpr instanceof ASTExpStringLiteral) {
@@ -138,12 +111,13 @@ final class ASTExpListLiteral extends ASTExpression {
         return result;
     }
     
+    @SuppressWarnings("unchecked")
     @Override
     protected ASTExpression deepCloneWithIdentifierReplaced_inner(
             String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) {
-		ArrayList clonedValues = (ArrayList) items.clone();
-		for (ListIterator iter = clonedValues.listIterator(); iter.hasNext(); ) {
-            iter.set(((ASTExpression) iter.next()).deepCloneWithIdentifierReplaced(
+        ArrayList<ASTExpression> clonedValues = (ArrayList<ASTExpression>) items.clone();
+		for (ListIterator<ASTExpression> iter = clonedValues.listIterator(); iter.hasNext(); ) {
+            iter.set(iter.next().deepCloneWithIdentifierReplaced(
                     replacedIdentifier, replacement, replacementState));
         }
         return new ASTExpListLiteral(clonedValues);

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
index 7b86e3e..edd80a5 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java
@@ -55,7 +55,7 @@ final class ASTExpRange extends ASTExpression {
                     begin, endType != END_SIZE_LIMITED ? lhoValue : begin + lhoValue,
                     endType == END_INCLUSIVE, endType == END_SIZE_LIMITED); 
         } else {
-            return new ListableRightUnboundedRangeModel(begin);
+            return new RightUnboundedRangeModel(begin);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
index 6b6d5e2..69bb34c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java
@@ -23,11 +23,11 @@ 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.TemplateHashModel;
+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.TemplateStringModel;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.impl.BeanModel;
 
 /**
@@ -75,7 +75,7 @@ abstract class ASTExpression extends ASTNode {
     }
 
     /**
-     * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection.
+     * @param seqTip Tip to display if the value type is not coercable, but it's iterable.
      */
     String evalAndCoerceToPlainText(Environment env, String seqTip) throws TemplateException {
         return _EvalUtils.coerceModelToPlainText(eval(env), this, seqTip, env);
@@ -86,7 +86,7 @@ abstract class ASTExpression extends ASTNode {
     }
 
     /**
-     * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection.
+     * @param seqTip Tip to display if the value type is not coercable, but it's iterable.
      */
     Object evalAndCoerceToStringOrMarkup(Environment env, String seqTip) throws TemplateException {
         return _EvalUtils.coerceModelToStringOrMarkup(eval(env), this, seqTip, env);
@@ -97,7 +97,7 @@ abstract class ASTExpression extends ASTNode {
     }
 
     /**
-     * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection.
+     * @param seqTip Tip to display if the value type is not coercable, but it's iterable.
      */
     String evalAndCoerceToStringOrUnsupportedMarkup(Environment env, String seqTip) throws TemplateException {
         return _EvalUtils.coerceModelToStringOrUnsupportedMarkup(eval(env), this, seqTip, env);
@@ -176,9 +176,9 @@ abstract class ASTExpression extends ASTNode {
 
     static boolean isEmpty(TemplateModel model) throws TemplateException {
         if (model instanceof BeanModel) {
-            return ((BeanModel) model).isEmpty();
-        } else if (model instanceof TemplateSequenceModel) {
-            return ((TemplateSequenceModel) model).size() == 0;
+            return ((BeanModel) model).isEmptyHash();
+        } else if (model instanceof TemplateCollectionModel) {
+            return ((TemplateCollectionModel) model).isEmptyCollection();
         } else if (model instanceof TemplateStringModel) {
             String s = ((TemplateStringModel) model).getAsString();
             return (s == null || s.length() == 0);
@@ -187,10 +187,10 @@ abstract class ASTExpression extends ASTNode {
         } else if (model instanceof TemplateMarkupOutputModel) { // Note: happens just after FTL string check
             TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) model;
             return mo.getOutputFormat().isEmpty(mo);
-        } else if (model instanceof TemplateCollectionModel) {
-            return !((TemplateCollectionModel) model).iterator().hasNext();
+        } else if (model instanceof TemplateIterableModel) {
+            return !((TemplateIterableModel) model).iterator().hasNext();
         } else if (model instanceof TemplateHashModel) {
-            return ((TemplateHashModel) model).isEmpty();
+            return ((TemplateHashModel) model).isEmptyHash();
         } else if (model instanceof TemplateNumberModel
                 || model instanceof TemplateDateModel
                 || model instanceof TemplateBooleanModel) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java
index 05efa98..9988200 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java
@@ -20,6 +20,9 @@
 package org.apache.freemarker.core;
 
 
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
 /**
  * A range between two integers (maybe 0 long).
  */
@@ -27,8 +30,7 @@ final class BoundedRangeModel extends RangeModel {
 
     private final int step, size;
     private final boolean rightAdaptive;
-    private final boolean affectedByStringSlicingBug;
-    
+
     /**
      * @param inclusiveEnd Tells if the {@code end} index is part of the range. 
      * @param rightAdaptive Tells if the right end of the range adapts to the size of the sliced value, if otherwise
@@ -39,14 +41,35 @@ final class BoundedRangeModel extends RangeModel {
         step = begin <= end ? 1 : -1;
         size = Math.abs(end - begin) + (inclusiveEnd ? 1 : 0);
         this.rightAdaptive = rightAdaptive;
-        affectedByStringSlicingBug = inclusiveEnd;
     }
 
     @Override
-    public int size() {
+    public int getCollectionSize() {
         return size;
     }
-    
+
+    @Override
+    public boolean isEmptyCollection() throws TemplateException {
+        return size == 0;
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new TemplateModelIterator() {
+            private long nextIndex;
+
+            @Override
+            public TemplateModel next() throws TemplateException {
+                return uncheckedGet(nextIndex++);
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return nextIndex < getCollectionSize();
+            }
+        };
+    }
+
     @Override
     int getStep() {
         return step;
@@ -62,9 +85,4 @@ final class BoundedRangeModel extends RangeModel {
         return rightAdaptive;
     }
 
-    @Override
-    boolean isAffactedByStringSlicingBug() {
-        return affectedByStringSlicingBug;
-    }
-    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java
new file mode 100644
index 0000000..15556f8
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java
@@ -0,0 +1,43 @@
+/*
+ * 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 org.apache.freemarker.core.model.TemplateIterableModel;
+import org.apache.freemarker.core.model.TemplateModel;
+
+abstract class BuiltInForIterable extends ASTExpBuiltIn {
+
+    @Override
+    TemplateModel _eval(Environment env)
+            throws TemplateException {
+        TemplateModel model = target.eval(env);
+        if (!(model instanceof TemplateIterableModel)) {
+            throw MessageUtils.newUnexpectedOperandTypeException(
+                    target, model,
+                    MessageUtils.EXPECTED_TYPE_ITERABLE_DESC,
+                    TemplateIterableModel.class,
+                    null, env);
+        }
+        return calculateResult((TemplateIterableModel) model);
+    }
+
+    abstract TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException;
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 bac6c7f..cb7cc73 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
@@ -23,6 +23,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 
 abstract class BuiltInForSequence extends ASTExpBuiltIn {
+
     @Override
     TemplateModel _eval(Environment env)
             throws TemplateException {
@@ -32,6 +33,7 @@ abstract class BuiltInForSequence extends ASTExpBuiltIn {
         }
         return calculateResult((TemplateSequenceModel) model);
     }
-    abstract TemplateModel calculateResult(TemplateSequenceModel tsm)
-            throws TemplateException;
+
+    abstract TemplateModel calculateResult(TemplateSequenceModel tsm) throws TemplateException;
+
 }
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 e87bd77..4d2a9d5 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,11 @@
 
 package org.apache.freemarker.core;
 
-import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 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.CollectionAndSequence;
+import org.apache.freemarker.core.model.impl.IterableAndSequence;
 
 /**
  * A holder for builtins that operate exclusively on hash left-hand value.
@@ -35,9 +35,9 @@ class BuiltInsForHashes {
         @Override
         TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env)
                 throws TemplateException, InvalidReferenceException {
-            TemplateCollectionModel keys = hashExModel.keys();
+            TemplateIterableModel keys = hashExModel.keys();
             if (keys == null) throw newNullPropertyException("keys", hashExModel, env);
-            return keys instanceof TemplateSequenceModel ? keys : new CollectionAndSequence(keys);
+            return keys instanceof TemplateSequenceModel ? keys : new IterableAndSequence(keys);
         }
         
     }
@@ -46,9 +46,9 @@ class BuiltInsForHashes {
         @Override
         TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env)
                 throws TemplateException, InvalidReferenceException {
-            TemplateCollectionModel values = hashExModel.values();
+            TemplateIterableModel values = hashExModel.values();
             if (values == null) throw newNullPropertyException("values", hashExModel, env);
-            return values instanceof TemplateSequenceModel ? values : new CollectionAndSequence(values);
+            return values instanceof TemplateSequenceModel ? values : new IterableAndSequence(values);
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
index d1f9761..9c48dc3 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java
@@ -25,8 +25,8 @@ import java.util.Date;
 
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
-import org.apache.freemarker.core.model.TemplateCollectionModelEx;
 import org.apache.freemarker.core.model.TemplateDateModel;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
@@ -171,7 +171,7 @@ class BuiltInsForMultipleTypes {
             }
     
             @Override
-            public boolean isEmpty() {
+            public boolean isEmptyHash() {
                 return false;
             }
     
@@ -261,21 +261,21 @@ class BuiltInsForMultipleTypes {
         }
     }
 
-    static class is_collectionBI extends ASTExpBuiltIn {
+    static class is_iterableBI extends ASTExpBuiltIn {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
             TemplateModel tm = target.eval(env);
             target.assertNonNull(tm, env);
-            return (tm instanceof TemplateCollectionModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+            return (tm instanceof TemplateIterableModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
         }
     }
 
-    static class is_collection_exBI extends ASTExpBuiltIn {
+    static class is_collectionBI extends ASTExpBuiltIn {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
             TemplateModel tm = target.eval(env);
             target.assertNonNull(tm, env);
-            return (tm instanceof TemplateCollectionModelEx) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
+            return (tm instanceof TemplateCollectionModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
         }
     }
 
@@ -317,16 +317,6 @@ class BuiltInsForMultipleTypes {
         }
     }
 
-    static class is_enumerableBI extends ASTExpBuiltIn {
-        @Override
-        TemplateModel _eval(Environment env) throws TemplateException {
-            TemplateModel tm = target.eval(env);
-            target.assertNonNull(tm, env);
-            return (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)
-                    ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
-        }
-    }
-
     static class is_hash_exBI extends ASTExpBuiltIn {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
@@ -345,15 +335,6 @@ class BuiltInsForMultipleTypes {
         }
     }
 
-    static class is_indexableBI extends ASTExpBuiltIn {
-        @Override
-        TemplateModel _eval(Environment env) throws TemplateException {
-            TemplateModel tm = target.eval(env);
-            target.assertNonNull(tm, env);
-            return (tm instanceof TemplateSequenceModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE;
-        }
-    }
-
     static class is_markup_outputBI extends ASTExpBuiltIn {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
@@ -434,21 +415,15 @@ class BuiltInsForMultipleTypes {
             TemplateModel model = target.eval(env);
 
             final int size;
-            if (model instanceof TemplateSequenceModel) {
-                size = ((TemplateSequenceModel) model).size();
-            } else if (model instanceof TemplateCollectionModelEx) {
-                size = ((TemplateCollectionModelEx) model).size();
+            if (model instanceof TemplateCollectionModel) {
+                size = ((TemplateCollectionModel) model).getCollectionSize();
             } else if (model instanceof TemplateHashModelEx) {
-                size = ((TemplateHashModelEx) model).size();
+                size = ((TemplateHashModelEx) model).getHashSize();
             } else {
                 throw MessageUtils.newUnexpectedOperandTypeException(
                         target, model,
-                        "extended-hash or sequence or extended collection",
-                        new Class[] {
-                                TemplateHashModelEx.class,
-                                TemplateSequenceModel.class,
-                                TemplateCollectionModelEx.class
-                        },
+                        "collection (like a sequence) or extended-hash",
+                        new Class[] { TemplateCollectionModel.class, TemplateHashModelEx.class },
                         null,
                         env);
             }
@@ -557,7 +532,7 @@ class BuiltInsForMultipleTypes {
             }
     
             @Override
-            public boolean isEmpty() {
+            public boolean isEmptyHash() {
                 return false;
             }
         }
@@ -608,7 +583,7 @@ class BuiltInsForMultipleTypes {
             }
     
             @Override
-            public boolean isEmpty() {
+            public boolean isEmptyHash() {
                 return false;
             }
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java
index b660a40..e598ea4 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java
@@ -64,7 +64,8 @@ class BuiltInsForNodes {
                     return this;
                 }
                 AncestorSequence result = new AncestorSequence(env);
-                for (int seqIdx = 0; seqIdx < size(); seqIdx++) {
+                int size = getCollectionSize();
+                for (int seqIdx = 0; seqIdx < size; seqIdx++) {
                     TemplateNodeModel tnm = (TemplateNodeModel) get(seqIdx);
                     String nodeName = tnm.getNodeName();
                     String nsURI = tnm.getNodeNamespace();