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:23 UTC

[4/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/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 e36d767..c28b385 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,16 +31,16 @@ 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;
+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.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.SequenceTemplateModelIterator;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl.SimpleString;
 import org.apache.freemarker.core.model.impl.TemplateModelListSequence;
@@ -48,7 +48,7 @@ import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._StringUtils;
 
 /**
- * A holder for builtins that operate exclusively on sequence or collection left-hand value.
+ * A holder for builtins that operate on sequence (or some even on iterable) left-hand value.
  */
 class BuiltInsForSequences {
     
@@ -66,7 +66,9 @@ class BuiltInsForSequences {
             public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
                     throws TemplateException {
                 int chunkSize = getNumberArgument(args, 0, this).intValue();
-
+                if (chunkSize < 1) {
+                    newArgumentValueException(0, "The value must be at least 1", this);
+                }
                 return new ChunkedSequence(tsm, chunkSize, args[1]);
             }
 
@@ -89,19 +91,16 @@ class BuiltInsForSequences {
             private ChunkedSequence(
                     TemplateSequenceModel wrappedTsm, int chunkSize, TemplateModel fillerItem)
                     throws TemplateException {
-                if (chunkSize < 1) {
-                    throw new TemplateException("The 1st argument to ?', key, ' (...) must be at least 1.");
-                }
                 this.wrappedTsm = wrappedTsm;
                 this.chunkSize = chunkSize;
                 this.fillerItem = fillerItem;
-                numberOfChunks = (wrappedTsm.size() + chunkSize - 1) / chunkSize; 
+                numberOfChunks = (wrappedTsm.getCollectionSize() + chunkSize - 1) / chunkSize;
             }
 
             @Override
             public TemplateModel get(final int chunkIndex)
                     throws TemplateException {
-                if (chunkIndex >= numberOfChunks) {
+                if (chunkIndex >= numberOfChunks || chunkIndex < 0) {
                     return null;
                 }
                 
@@ -110,33 +109,51 @@ class BuiltInsForSequences {
                     private final int baseIndex = chunkIndex * chunkSize;
 
                     @Override
-                    public TemplateModel get(int relIndex)
-                            throws TemplateException {
+                    public TemplateModel get(int relIndex) throws TemplateException {
+                        if (relIndex < 0) {
+                            return null;
+                        }
                         int absIndex = baseIndex + relIndex;
-                        if (absIndex < wrappedTsm.size()) {
+                        if (absIndex < wrappedTsm.getCollectionSize()) {
                             return wrappedTsm.get(absIndex);
                         } else {
-                            return absIndex < numberOfChunks * chunkSize
-                                ? fillerItem
-                                : null;
+                            return absIndex < numberOfChunks * chunkSize ? fillerItem : null;
                         }
                     }
 
                     @Override
-                    public int size() throws TemplateException {
+                    public int getCollectionSize() throws TemplateException {
                         return fillerItem != null || chunkIndex + 1 < numberOfChunks
                                 ? chunkSize
-                                : wrappedTsm.size() - baseIndex;
+                                : wrappedTsm.getCollectionSize() - baseIndex;
+                    }
+
+                    @Override
+                    public boolean isEmptyCollection() throws TemplateException {
+                        return getCollectionSize() == 0;
+                    }
+
+                    @Override
+                    public TemplateModelIterator iterator() throws TemplateException {
+                        return new SequenceTemplateModelIterator(this);
                     }
-                    
                 };
             }
 
             @Override
-            public int size() throws TemplateException {
+            public int getCollectionSize() throws TemplateException {
                 return numberOfChunks;
             }
-            
+
+            @Override
+            public boolean isEmptyCollection() throws TemplateException {
+                return numberOfChunks == 0;
+            }
+
+            @Override
+            public TemplateModelIterator iterator() throws TemplateException {
+                return new SequenceTemplateModelIterator(this);
+            }
         }
         
         @Override
@@ -146,56 +163,27 @@ class BuiltInsForSequences {
         
     }
     
-    static class firstBI extends ASTExpBuiltIn {
+    static class firstBI extends BuiltInForIterable {
         
         @Override
-        TemplateModel _eval(Environment env)
-                throws TemplateException {
-            TemplateModel model = target.eval(env);
-            // In 2.3.x only, we prefer TemplateSequenceModel for
-            // backward compatibility. In 2.4.x, we prefer TemplateCollectionModel. 
-            if (model instanceof TemplateSequenceModel) {
-                return calculateResultForSequence((TemplateSequenceModel) model);
-            } else if (model instanceof TemplateCollectionModel) {
-                return calculateResultForColletion((TemplateCollectionModel) model);
-            } else {
-                throw MessageUtils.newUnexpectedOperandTypeException(
-                        target, model,
-                        MessageUtils.SEQUENCE_OR_COLLECTION,
-                        MessageUtils.EXPECTED_TYPES_SEQUENCE_OR_COLLECTION,
-                        null, env);
-            }
-        }        
-        
-        private TemplateModel calculateResultForSequence(TemplateSequenceModel seq)
-        throws TemplateException {
-            if (seq.size() == 0) {
-                return null;
-            }
-            return seq.get(0);
-        }
-        
-        private TemplateModel calculateResultForColletion(TemplateCollectionModel coll)
-        throws TemplateException {
-            TemplateModelIterator iter = coll.iterator();
+        TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException {
+            TemplateModelIterator iter = model.iterator();
             if (!iter.hasNext()) {
                 return null;
             }
             return iter.next();
         }
-        
+
     }
 
-    static class joinBI extends ASTExpBuiltIn {
+    static class joinBI extends BuiltInForIterable {
         
-        private class BIMethodForCollection extends BuiltInCallableImpl implements TemplateFunctionModel {
+        private class BIMethodForIterable extends BuiltInCallableImpl implements TemplateFunctionModel {
             
-            private final Environment env;
-            private final TemplateCollectionModel coll;
+            private final TemplateIterableModel iterable;
 
-            private BIMethodForCollection(Environment env, TemplateCollectionModel coll) {
-                this.env = env;
-                this.coll = coll;
+            private BIMethodForIterable(TemplateIterableModel iterable) {
+                this.iterable = iterable;
             }
 
             @Override
@@ -207,7 +195,7 @@ class BuiltInsForSequences {
 
                 StringBuilder sb = new StringBuilder();
 
-                TemplateModelIterator it = coll.iterator();
+                TemplateModelIterator it = iterable.iterator();
 
                 int idx = 0;
                 boolean hadItem = false;
@@ -247,23 +235,12 @@ class BuiltInsForSequences {
         }
 
         @Override
-        TemplateModel _eval(Environment env) throws TemplateException {
-            TemplateModel model = target.eval(env);
-            if (model instanceof TemplateCollectionModel) {
-                if (model instanceof RightUnboundedRangeModel) {
-                    throw new TemplateException(
-                            "The sequence to join was right-unbounded numerical range, thus it's infinitely long.");
-                }
-                return new BIMethodForCollection(env, (TemplateCollectionModel) model);
-            } else if (model instanceof TemplateSequenceModel) {
-                return new BIMethodForCollection(env, new CollectionAndSequence((TemplateSequenceModel) model));
-            } else {
-                throw MessageUtils.newUnexpectedOperandTypeException(
-                        target, model,
-                        MessageUtils.SEQUENCE_OR_COLLECTION,
-                        MessageUtils.EXPECTED_TYPES_SEQUENCE_OR_COLLECTION,
-                        null, env);
+        TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException {
+            if (model instanceof RightUnboundedRangeModel) {
+                throw new TemplateException(
+                        "The sequence to join was right-unbounded numerical range, thus it's infinitely long.");
             }
+            return new BIMethodForIterable((TemplateIterableModel) model);
         }
    
     }
@@ -272,10 +249,11 @@ class BuiltInsForSequences {
         @Override
         TemplateModel calculateResult(TemplateSequenceModel tsm)
         throws TemplateException {
-            if (tsm.size() == 0) {
+            int size = tsm.getCollectionSize();
+            if (size == 0) {
                 return null;
             }
-            return tsm.get(tsm.size() - 1);
+            return tsm.get(size - 1);
         }
     }
 
@@ -289,12 +267,22 @@ class BuiltInsForSequences {
 
             @Override
             public TemplateModel get(int index) throws TemplateException {
-                return seq.get(seq.size() - 1 - index);
+                return seq.get(seq.getCollectionSize() - 1 - index);
+            }
+
+            @Override
+            public int getCollectionSize() throws TemplateException {
+                return seq.getCollectionSize();
             }
 
             @Override
-            public int size() throws TemplateException {
-                return seq.size();
+            public boolean isEmptyCollection() throws TemplateException {
+                return seq.isEmptyCollection();
+            }
+
+            @Override
+            public TemplateModelIterator iterator() throws TemplateException {
+                return new SequenceTemplateModelIterator(this);
             }
         }
 
@@ -308,25 +296,24 @@ class BuiltInsForSequences {
         }
     }
 
-    static class seq_containsBI extends ASTExpBuiltIn {
-        private class BIMethodForCollection extends BuiltInCallableImpl implements TemplateFunctionModel {
-            private TemplateCollectionModel m_coll;
-            private Environment m_env;
+    static class seq_containsBI extends BuiltInForIterable {
+        private class BIMethod extends BuiltInCallableImpl implements TemplateFunctionModel {
+            private TemplateIterableModel iterable;
 
-            private BIMethodForCollection(TemplateCollectionModel coll, Environment env) {
-                m_coll = coll;
-                m_env = env;
+            private BIMethod(TemplateIterableModel coll) {
+                iterable = coll;
             }
 
             @Override
             public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
                     throws TemplateException {
                 TemplateModel arg = args[0];
-                TemplateModelIterator it = m_coll.iterator();
+                TemplateModelIterator it = iterable.iterator();
                 int idx = 0;
                 while (it.hasNext()) {
-                    if (modelsEqual(idx, it.next(), arg, m_env))
+                    if (modelsEqual(idx, it.next(), arg, env)) {
                         return TemplateBooleanModel.TRUE;
+                    }
                     idx++;
                 }
                 return TemplateBooleanModel.FALSE;
@@ -339,111 +326,38 @@ class BuiltInsForSequences {
 
         }
 
-        private class BIMethodForSequence extends BuiltInCallableImpl implements TemplateFunctionModel {
-            private TemplateSequenceModel m_seq;
-            private Environment m_env;
-
-            private BIMethodForSequence(TemplateSequenceModel seq, Environment env) {
-                m_seq = seq;
-                m_env = env;
-            }
-
-            @Override
-            public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
-                    throws TemplateException {
-                TemplateModel arg = args[0];
-                int size = m_seq.size();
-                for (int i = 0; i < size; i++) {
-                    if (modelsEqual(i, m_seq.get(i), arg, m_env))
-                        return TemplateBooleanModel.TRUE;
-                }
-                return TemplateBooleanModel.FALSE;
-            }
-
-            @Override
-            public ArgumentArrayLayout getFunctionArgumentArrayLayout() {
-                return ArgumentArrayLayout.SINGLE_POSITIONAL_PARAMETER;
-            }
-
-        }
-    
         @Override
-        TemplateModel _eval(Environment env)
-                throws TemplateException {
-            TemplateModel model = target.eval(env);
-            // In 2.3.x only, we prefer TemplateSequenceModel for
-            // backward compatibility. In 2.4.x, we prefer TemplateCollectionModel. 
-            if (model instanceof TemplateSequenceModel) {
-                return new BIMethodForSequence((TemplateSequenceModel) model, env);
-            } else if (model instanceof TemplateCollectionModel) {
-                return new BIMethodForCollection((TemplateCollectionModel) model, env);
-            } else {
-                throw MessageUtils.newUnexpectedOperandTypeException(
-                        target, model,
-                        MessageUtils.SEQUENCE_OR_COLLECTION,
-                        MessageUtils.EXPECTED_TYPES_SEQUENCE_OR_COLLECTION,
-                        null, env);
-            }
+        TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException  {
+            return new BIMethod((TemplateIterableModel) model);
         }
     
     }
     
-    static class seq_index_ofBI extends ASTExpBuiltIn {
+    static class seq_index_ofBI extends BuiltInForIterable {
         
         private class BIMethod extends BuiltInCallableImpl implements TemplateFunctionModel {
             
-            final TemplateSequenceModel m_seq;
-            final TemplateCollectionModel m_col;
-            final Environment m_env;
+            final TemplateIterableModel iterable;
 
-            private BIMethod(Environment env)
+            private BIMethod(TemplateIterableModel iterable)
                     throws TemplateException {
-                TemplateModel model = target.eval(env);
-                m_seq = model instanceof TemplateSequenceModel
-                        ? (TemplateSequenceModel) model
-                        : null;
-                // [FM3] Rework the below
-                // In 2.3.x only, we deny the possibility of collection
-                // access if there's sequence access. This is so to minimize
-                // the change of compatibility issues; without this, objects
-                // that implement both the sequence and collection interfaces
-                // would suddenly start using the collection interface, and if
-                // that's buggy that would surface now, breaking the application
-                // that despite its bugs has worked earlier.
-                m_col = m_seq == null && model instanceof TemplateCollectionModel
-                        ? (TemplateCollectionModel) model
-                        : null;
-                if (m_seq == null && m_col == null) {
-                    throw MessageUtils.newUnexpectedOperandTypeException(
-                            target, model,
-                            MessageUtils.SEQUENCE_OR_COLLECTION,
-                            MessageUtils.EXPECTED_TYPES_SEQUENCE_OR_COLLECTION,
-                            null, env);
-                }
-                
-                m_env = env;
+                this.iterable = iterable;
             }
 
             @Override
             public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
                     throws TemplateException {
-                TemplateModel target = args[0];
-                Number startIndex = getOptionalNumberArgument(args, 1, this);
+                TemplateModel searched = args[0];
+                Integer startIndex = getOptionalIntArgument(args, 1, this);
                 int foundAtIdx;
                 if (startIndex != null) {
-                    // TODO [FM3] Prefer Col?
-                    // In 2.3.x only, we prefer TemplateSequenceModel for
-                    // backward compatibility:
-                    foundAtIdx = m_seq != null
-                            ? findInSeq(target, startIndex.intValue())
-                            : findInCol(target, startIndex.intValue());
+                    foundAtIdx = iterable instanceof TemplateSequenceModel
+                            ? findInSeq(searched, (TemplateSequenceModel) iterable, startIndex, env)
+                            : findInIter(searched, startIndex, env);
                 } else {
-                    // TODO [FM3] Prefer Col?
-                    // In 2.3.x only, we prefer TemplateSequenceModel for
-                    // backward compatibility:
-                    foundAtIdx = m_seq != null
-                            ? findInSeq(target)
-                            : findInCol(target);
+                    foundAtIdx = iterable instanceof TemplateSequenceModel
+                            ? findInSeq(searched, (TemplateSequenceModel) iterable, env)
+                            : findInIter(searched, env);
                 }
                 return foundAtIdx == -1 ? TemplateNumberModel.MINUS_ONE : new SimpleNumber(foundAtIdx);
             }
@@ -453,37 +367,41 @@ class BuiltInsForSequences {
                 return ArgumentArrayLayout.TWO_POSITIONAL_PARAMETERS;
             }
 
-            int findInCol(TemplateModel target) throws TemplateException {
-                return findInCol(target, 0, Integer.MAX_VALUE);
+            int findInIter(TemplateModel searched, Environment env) throws TemplateException {
+                return findInIter(searched, 0, Integer.MAX_VALUE, env);
             }
             
-            int findInCol(TemplateModel target, int startIndex)
+            int findInIter(TemplateModel searched, int startIndex, Environment env)
                     throws TemplateException {
-                if (m_dir == 1) {
-                    return findInCol(target, startIndex, Integer.MAX_VALUE);
+                if (findFirst) {
+                    return findInIter(searched, startIndex, Integer.MAX_VALUE, env);
                 } else {
-                    return findInCol(target, 0, startIndex);
+                    return findInIter(searched, 0, startIndex, env);
                 }
             }
         
-            int findInCol(TemplateModel target,
-                    final int allowedRangeStart, final int allowedRangeEnd)
+            int findInIter(TemplateModel searched,
+                    final int allowedRangeStart, final int allowedRangeEnd, Environment env)
                     throws TemplateException {
                 if (allowedRangeEnd < 0) return -1;
                 
-                TemplateModelIterator it = m_col.iterator();
+                TemplateModelIterator it = iterable.iterator();
                 
                 int foundAtIdx = -1;  // -1 is the return value for "not found"
                 int idx = 0; 
                 searchItem: while (it.hasNext()) {
-                    if (idx > allowedRangeEnd) break searchItem;
+                    if (idx > allowedRangeEnd) {
+                        break searchItem;
+                    }
                     
                     TemplateModel current = it.next();
                     if (idx >= allowedRangeStart) {
-                        if (modelsEqual(idx, current, target, m_env)) {
+                        if (modelsEqual(idx, current, searched, env)) {
                             foundAtIdx = idx;
-                            if (m_dir == 1) break searchItem; // "find first"
-                            // Otherwise it's "find last".
+                            // Don't stop if it's "find last".
+                            if (findFirst) {
+                                break searchItem;
+                            }
                         }
                     }
                     idx++;
@@ -491,25 +409,26 @@ class BuiltInsForSequences {
                 return foundAtIdx;
             }
 
-            int findInSeq(TemplateModel target)
+            int findInSeq(TemplateModel searched, TemplateSequenceModel seq, Environment env)
                     throws TemplateException {
-                final int seqSize = m_seq.size();
+                final int seqSize = seq.getCollectionSize();
                 final int actualStartIndex;
-                
-                if (m_dir == 1) {
+
+                if (findFirst) {
                     actualStartIndex = 0;
                 } else {
                     actualStartIndex = seqSize - 1;
                 }
-            
-                return findInSeq(target, actualStartIndex, seqSize); 
+
+                return findInSeq(searched, seq, actualStartIndex, seqSize, env);
             }
 
-            private int findInSeq(TemplateModel target, int startIndex)
+            private int findInSeq(
+                    TemplateModel searched, TemplateSequenceModel seq, int startIndex, Environment env)
                     throws TemplateException {
-                int seqSize = m_seq.size();
+                int seqSize = seq.getCollectionSize();
                 
-                if (m_dir == 1) {
+                if (findFirst) {
                     if (startIndex >= seqSize) {
                         return -1;
                     }
@@ -525,19 +444,19 @@ class BuiltInsForSequences {
                     }
                 }
                 
-                return findInSeq(target, startIndex, seqSize); 
+                return findInSeq(searched, seq, startIndex, seqSize, env);
             }
             
             private int findInSeq(
-                    TemplateModel target, int scanStartIndex, int seqSize)
+                    TemplateModel searched, TemplateSequenceModel seq, int scanStartIndex, int seqSize, Environment env)
                     throws TemplateException {
-                if (m_dir == 1) {
+                if (findFirst) {
                     for (int i = scanStartIndex; i < seqSize; i++) {
-                        if (modelsEqual(i, m_seq.get(i), target, m_env)) return i;
+                        if (modelsEqual(i, seq.get(i), searched, env)) return i;
                     }
                 } else {
                     for (int i = scanStartIndex; i >= 0; i--) {
-                        if (modelsEqual(i, m_seq.get(i), target, m_env)) return i;
+                        if (modelsEqual(i, seq.get(i), searched, env)) return i;
                     }
                 }
                 return -1;
@@ -545,16 +464,15 @@ class BuiltInsForSequences {
             
         }
 
-        private int m_dir;
+        private boolean findFirst;
 
-        seq_index_ofBI(int dir) {
-            m_dir = dir;
+        seq_index_ofBI(boolean findFirst) {
+            this.findFirst = findFirst;
         }
 
         @Override
-        TemplateModel _eval(Environment env)
-                throws TemplateException {
-            return new BIMethod(env);
+        TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException {
+            return new BIMethod(model);
         }
     }
 
@@ -567,7 +485,8 @@ class BuiltInsForSequences {
             }
 
             @Override
-            public TemplateModel execute(TemplateModel[] args, CallPlace callPlace, Environment env)
+            public TemplateModel execute(
+                    TemplateModel[] args, CallPlace callPlace, Environment env)
                     throws TemplateException {
                 String[] subvars;
                 TemplateModel obj = args[0];
@@ -575,10 +494,11 @@ class BuiltInsForSequences {
                     subvars = new String[] { ((TemplateStringModel) obj).getAsString() };
                 } else if (obj instanceof TemplateSequenceModel) {
                     TemplateSequenceModel seq = (TemplateSequenceModel) obj;
-                    int ln = seq.size();
+                    int ln = seq.getCollectionSize();
                     subvars = new String[ln];
+                    TemplateModelIterator iter = seq.iterator();
                     for (int i = 0; i < ln; i++) {
-                        TemplateModel item = seq.get(i);
+                        TemplateModel item = iter.next();
                         if (!(item instanceof TemplateStringModel)) {
                             throw new TemplateException(
                                     "The argument to ?", key, "(key), when it's a sequence, must be a "
@@ -711,7 +631,7 @@ class BuiltInsForSequences {
          */
         static TemplateSequenceModel sort(TemplateSequenceModel seq, String[] keyNames)
                 throws TemplateException {
-            int ln = seq.size();
+            int ln = seq.getCollectionSize();
             if (ln == 0) return seq;
             
             ArrayList res = new ArrayList(ln);
@@ -721,8 +641,9 @@ class BuiltInsForSequences {
             // Copy the Seq into a Java List[KVP] (also detects key type at the 1st item):
             int keyType = KEY_TYPE_NOT_YET_DETECTED;
             Comparator keyComparator = null;
+            TemplateModelIterator iter = seq.iterator();
             for (int i = 0; i < ln; i++) {
-                final TemplateModel item = seq.get(i);
+                final TemplateModel item = iter.next();
                 TemplateModel key = item;
                 for (int keyNameI = 0; keyNameI < keyNamesLn; keyNameI++) {
                     try {
@@ -757,8 +678,7 @@ class BuiltInsForSequences {
                     } else if (key instanceof TemplateNumberModel) {
                         keyType = KEY_TYPE_NUMBER;
                         keyComparator = new NumericalKVPComparator(
-                                Environment.getCurrentEnvironment()
-                                        .getArithmeticEngine());
+                                Environment.getCurrentEnvironment().getArithmeticEngine());
                     } else if (key instanceof TemplateDateModel) {
                         keyType = KEY_TYPE_DATE;
                         keyComparator = new DateKVPComparator();
@@ -877,8 +797,7 @@ class BuiltInsForSequences {
     }
 
     private static boolean modelsEqual(
-            int seqItemIndex, TemplateModel seqItem, TemplateModel searchedItem,
-            Environment env)
+            int seqItemIndex, TemplateModel seqItem, TemplateModel searchedItem, Environment env)
             throws TemplateException {
         try {
             return _EvalUtils.compare(
@@ -894,7 +813,7 @@ class BuiltInsForSequences {
                     " to the searched item:\n", new _DelayedGetMessage(ex));
         }
     }
- 
+
     // Can't be instantiated
     private BuiltInsForSequences() { }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java
index bbdb18f..80429b8 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsBasic.java
@@ -95,7 +95,7 @@ class BuiltInsForStringsBasic {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
             return new BIMethod(target.evalAndCoerceToStringOrUnsupportedMarkup(env,
-                    "For sequences/collections (lists and such) use \"?seqContains\" instead."));
+                    "For iterables (like sequences) use \"?seqContains\" instead."));
         }
     }
 
@@ -252,7 +252,7 @@ class BuiltInsForStringsBasic {
         @Override
         TemplateModel _eval(Environment env) throws TemplateException {
             return new BIMethod(target.evalAndCoerceToStringOrUnsupportedMarkup(env,
-                    "For sequences/collections (lists and such) use \"?seqIndexOf\" instead."));
+                    "For iterables (like seqiences) use \"?seqIndexOf\" instead."));
         }
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
index d1c42cc..26821a3 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsMisc.java
@@ -151,7 +151,7 @@ class BuiltInsForStringsMisc {
             if (model instanceof TemplateSequenceModel) {
                 sourceExpr = ((ASTExpression) new ASTExpDynamicKeyName(target, new ASTExpNumberLiteral(0))
                         .copyLocationFrom(target));
-                if (((TemplateSequenceModel) model).size() > 1) {
+                if (((TemplateSequenceModel) model).getCollectionSize() > 1) {
                     id = ((ASTExpression) new ASTExpDynamicKeyName(target, new ASTExpNumberLiteral(1))
                             .copyLocationFrom(target)).evalAndCoerceToPlainText(env);
                 }
@@ -168,7 +168,6 @@ class BuiltInsForStringsMisc {
             
             final Template interpretedTemplate;
             try {
-                ParsingConfiguration pCfg = parentTemplate.getParsingConfiguration();
                 // pCfg.outputFormat+autoEscapingPolicy is exceptional: it's inherited from the lexical context
                 interpretedTemplate = new Template(
                         (parentTemplate.getLookupName() != null ? parentTemplate.getLookupName() : "nameless_template") + "->" + id,

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java
index cb09e95..679b641 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForStringsRegexp.java
@@ -27,12 +27,12 @@ import java.util.regex.Pattern;
 
 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.TemplateFunctionModel;
 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.TemplateStringModel;
+import org.apache.freemarker.core.model.impl.SequenceTemplateModelIterator;
 import org.apache.freemarker.core.model.impl.SimpleString;
 import org.apache.freemarker.core.util._StringUtils;
 
@@ -152,8 +152,7 @@ class BuiltInsForStringsRegexp {
     
     // Represents the match
   
-    static class RegexMatchModel 
-    implements TemplateBooleanModel, TemplateCollectionModel, TemplateSequenceModel {
+    static class RegexMatchModel implements TemplateBooleanModel, TemplateSequenceModel {
         static class MatchWithGroups implements TemplateStringModel {
             final String matchedInputPart;
             final String[] groups;
@@ -172,15 +171,14 @@ class BuiltInsForStringsRegexp {
                 return matchedInputPart;
             }
         }
+
         final Pattern pattern;
-        
         final String input;
+
         private Matcher firedEntireInputMatcher;
         private Boolean entireInputMatched;
-        
         private TemplateSequenceModel entireInputMatchGroups;
-        
-        private ArrayList matchingInputParts;
+        private ArrayList<TemplateModel> matchingInputParts;
         
         RegexMatchModel(Pattern pattern, String input) {
             this.pattern = pattern;
@@ -189,17 +187,34 @@ class BuiltInsForStringsRegexp {
         
         @Override
         public TemplateModel get(int i) throws TemplateException {
-            ArrayList matchingInputParts = this.matchingInputParts;
+            if (i < 0) {
+                return  null;
+            }
+            ArrayList<TemplateModel> matchingInputParts = this.matchingInputParts;
             if (matchingInputParts == null) {
                 matchingInputParts = getMatchingInputPartsAndStoreResults();
             }
-            return (TemplateModel) matchingInputParts.get(i);
+            return i < matchingInputParts.size() ? matchingInputParts.get(i) : null;
         }
-        
+
+        @Override
+        public int getCollectionSize() throws TemplateException {
+            ArrayList<TemplateModel> matchingInputParts = this.matchingInputParts;
+            if (matchingInputParts == null) {
+                matchingInputParts = getMatchingInputPartsAndStoreResults();
+            }
+            return matchingInputParts.size();
+        }
+
+        @Override
+        public boolean isEmptyCollection() throws TemplateException {
+            return getCollectionSize() == 0;
+        }
+
         @Override
         public boolean getAsBoolean() {
             Boolean result = entireInputMatched;
-            return result != null ? result.booleanValue() : isEntrieInputMatchesAndStoreResults();
+            return result != null ? result : isEntireInputMatchesAndStoreResults();
         }
         
         TemplateModel getGroups() {
@@ -207,7 +222,7 @@ class BuiltInsForStringsRegexp {
            if (entireInputMatchGroups == null) {
                Matcher t = firedEntireInputMatcher;
                if (t == null) {
-                   isEntrieInputMatchesAndStoreResults();
+                   isEntireInputMatchesAndStoreResults();
                    t = firedEntireInputMatcher;
                }
                final Matcher firedEntireInputMatcher = t;
@@ -217,8 +232,7 @@ class BuiltInsForStringsRegexp {
                     @Override
                     public TemplateModel get(int i) throws TemplateException {
                         try {
-                            // Avoid IndexOutOfBoundsException:
-                            if (i > firedEntireInputMatcher.groupCount()) {
+                            if (i < 0 || i > firedEntireInputMatcher.groupCount()) {
                                 return null;
                             }
 
@@ -229,22 +243,27 @@ class BuiltInsForStringsRegexp {
                     }
                     
                     @Override
-                    public int size() throws TemplateException {
-                        try {
-                            return firedEntireInputMatcher.groupCount() + 1;
-                        } catch (Exception e) {
-                            throw new TemplateException("Failed to get regular expression match group count", e);
-                        }
+                    public int getCollectionSize() throws TemplateException {
+                        return firedEntireInputMatcher.groupCount() + 1;
+                    }
+
+                    @Override
+                    public boolean isEmptyCollection() throws TemplateException {
+                        return getCollectionSize() == 0;
+                    }
+
+                    @Override
+                    public TemplateModelIterator iterator() throws TemplateException {
+                        return new SequenceTemplateModelIterator(this);
                     }
-                    
                 };
                 this.entireInputMatchGroups = entireInputMatchGroups;
             }
             return entireInputMatchGroups;
         }
         
-        private ArrayList getMatchingInputPartsAndStoreResults() throws TemplateException {
-            ArrayList matchingInputParts = new ArrayList();
+        private ArrayList<TemplateModel> getMatchingInputPartsAndStoreResults() throws TemplateException {
+            ArrayList<TemplateModel> matchingInputParts = new ArrayList<>();
             
             Matcher matcher = pattern.matcher(input);
             while (matcher.find()) {
@@ -255,17 +274,17 @@ class BuiltInsForStringsRegexp {
             return matchingInputParts;
         }
         
-        private boolean isEntrieInputMatchesAndStoreResults() {
+        private boolean isEntireInputMatchesAndStoreResults() {
             Matcher matcher = pattern.matcher(input);
             boolean matches = matcher.matches();
             firedEntireInputMatcher = matcher;
-            entireInputMatched = Boolean.valueOf(matches);
+            entireInputMatched = matches;
             return matches;
         }
         
         @Override
         public TemplateModelIterator iterator() {
-            final ArrayList matchingInputParts = this.matchingInputParts;
+            final ArrayList<TemplateModel> matchingInputParts = this.matchingInputParts;
             if (matchingInputParts == null) {
                 final Matcher matcher = pattern.matcher(input);
                 return new TemplateModelIterator() {
@@ -275,7 +294,7 @@ class BuiltInsForStringsRegexp {
                     
                     @Override
                     public boolean hasNext() {
-                        final ArrayList matchingInputParts = RegexMatchModel.this.matchingInputParts;
+                        final ArrayList<TemplateModel> matchingInputParts = RegexMatchModel.this.matchingInputParts;
                         if (matchingInputParts == null) {
                             return hasFindInfo;
                         } else {
@@ -285,7 +304,7 @@ class BuiltInsForStringsRegexp {
                     
                     @Override
                     public TemplateModel next() throws TemplateException {
-                        final ArrayList matchingInputParts = RegexMatchModel.this.matchingInputParts;
+                        final ArrayList<TemplateModel> matchingInputParts = RegexMatchModel.this.matchingInputParts;
                         if (matchingInputParts == null) {
                             if (!hasFindInfo) {
                                 throw new TemplateException("There were no more regular expression matches");
@@ -295,11 +314,7 @@ class BuiltInsForStringsRegexp {
                             hasFindInfo = matcher.find();
                             return result;
                         } else {
-                            try {
-                                return (TemplateModel) matchingInputParts.get(nextIdx++);
-                            } catch (IndexOutOfBoundsException e) {
-                                throw new TemplateException("There were no more regular expression matches", e);
-                            }
+                            return matchingInputParts.get(nextIdx++);
                         }
                     }
                     
@@ -316,24 +331,12 @@ class BuiltInsForStringsRegexp {
                     
                     @Override
                     public TemplateModel next() throws TemplateException {
-                        try {
-                            return (TemplateModel) matchingInputParts.get(nextIdx++);
-                        } catch (IndexOutOfBoundsException e) {
-                            throw new TemplateException("There were no more regular expression matches", e);
-                        }
+                        return matchingInputParts.get(nextIdx++);
                     }
                 };
             }
         }
         
-        @Override
-        public int size() throws TemplateException {
-            ArrayList matchingInputParts = this.matchingInputParts;
-            if (matchingInputParts == null) {
-                matchingInputParts = getMatchingInputPartsAndStoreResults();
-            }
-            return matchingInputParts.size();
-        }
     }
 
     // Can't be instantiated

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
index c0af937..26d5a0e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/Environment.java
@@ -48,7 +48,7 @@ import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateCallableModel;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateDateModel;
 import org.apache.freemarker.core.model.TemplateDirectiveModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
@@ -669,8 +669,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             }
         }
         TemplateSequenceModel children = node.getChildNodes();
-        if (children == null) return;
-        for (int i = 0; i < children.size(); i++) {
+        if (children == null) {
+            return;
+        }
+        int size = children.getCollectionSize();
+        for (int i = 0; i < size; i++) {
             TemplateNodeModel child = (TemplateNodeModel) children.get(i);
             if (child != null) {
                 invokeNodeHandlerFor(child, namespaces);
@@ -2195,7 +2198,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         final TemplateHashModel result = new TemplateHashModel() {
 
             @Override
-            public boolean isEmpty() {
+            public boolean isEmptyHash() {
                 return false;
             }
 
@@ -2213,8 +2216,8 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             return new TemplateHashModelEx() {
 
                 @Override
-                public boolean isEmpty() throws TemplateException {
-                    return result.isEmpty();
+                public boolean isEmptyHash() throws TemplateException {
+                    return result.isEmptyHash();
                 }
 
                 @Override
@@ -2226,18 +2229,18 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
                 // configuration shared variables even though
                 // the hash will return them, if only for BWC reasons
                 @Override
-                public TemplateCollectionModel values() throws TemplateException {
+                public TemplateIterableModel values() throws TemplateException {
                     return ((TemplateHashModelEx) rootDataModel).values();
                 }
 
                 @Override
-                public TemplateCollectionModel keys() throws TemplateException {
+                public TemplateIterableModel keys() throws TemplateException {
                     return ((TemplateHashModelEx) rootDataModel).keys();
                 }
 
                 @Override
-                public int size() throws TemplateException {
-                    return ((TemplateHashModelEx) rootDataModel).size();
+                public int getHashSize() throws TemplateException {
+                    return ((TemplateHashModelEx) rootDataModel).getHashSize();
                 }
             };
         }
@@ -2253,7 +2256,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         return new TemplateHashModel() {
 
             @Override
-            public boolean isEmpty() {
+            public boolean isEmptyHash() {
                 return false;
             }
 
@@ -2333,7 +2336,8 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
             throws TemplateException {
         TemplateDirectiveModel result = null;
         int i;
-        for (i = startIndex; i < nodeNamespaces.size(); i++) {
+        int size = nodeNamespaces.getCollectionSize();
+        for (i = startIndex; i < size; i++) {
             Namespace ns = null;
             try {
                 ns = (Namespace) nodeNamespaces.get(i);
@@ -2850,25 +2854,25 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         }
 
         @Override
-        public int size() {
+        public int getHashSize() {
             ensureInitializedRTE();
-            return super.size();
+            return super.getHashSize();
         }
 
         @Override
-        public boolean isEmpty() {
+        public boolean isEmptyHash() {
             ensureInitializedRTE();
-            return super.isEmpty();
+            return super.isEmptyHash();
         }
 
         @Override
-        public TemplateCollectionModel keys() {
+        public TemplateIterableModel keys() {
             ensureInitializedRTE();
             return super.keys();
         }
 
         @Override
-        public TemplateCollectionModel values() {
+        public TemplateIterableModel values() {
             ensureInitializedRTE();
             return super.values();
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java b/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java
index 20d2c10..2cd4fd1 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/InvalidReferenceException.java
@@ -65,7 +65,7 @@ public class InvalidReferenceException extends TemplateException {
     private static final String TIP_JSP_TAGLIBS =
             "The \"JspTaglibs\" variable isn't a core FreeMarker feature; "
             + "it's only available when templates are invoked through org.apache.freemarker.servlet.FreemarkerServlet"
-            + " (or other custom FreeMarker-JSP integration solution).";
+            + " (or through some other custom FreeMarker-JSP integration solution).";
     
     /**
      * Creates and invalid reference exception that contains no information about what was missing or null.

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
deleted file mode 100644
index 0b7e94e..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ListableRightUnboundedRangeModel.java
+++ /dev/null
@@ -1,94 +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;
-
-import java.math.BigInteger;
-
-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.impl.SimpleNumber;
-
-/**
- * This is the model used for right-unbounded ranges since Incompatible Improvements 2.3.21.
- */
-final class ListableRightUnboundedRangeModel extends RightUnboundedRangeModel implements TemplateCollectionModel {
-
-    ListableRightUnboundedRangeModel(int begin) {
-        super(begin);
-    }
-
-    @Override
-    public int size() throws TemplateException {
-        return Integer.MAX_VALUE;
-    }
-
-    @Override
-    public TemplateModelIterator iterator() throws TemplateException {
-        return new TemplateModelIterator() {
-            boolean needInc;
-            int nextType = 1;
-            int nextInt = getBegining();
-            long nextLong;
-            BigInteger nextBigInteger;
-
-            @Override
-            public TemplateModel next() throws TemplateException {
-                if (needInc) {
-                    switch (nextType) {
-                    case 1:
-                        if (nextInt < Integer.MAX_VALUE) {
-                            nextInt++;
-                        } else {
-                            nextType = 2;
-                            nextLong = nextInt + 1L;
-                        }
-                        break;
-                        
-                    case 2:
-                        if (nextLong < Long.MAX_VALUE) {
-                            nextLong++;
-                        } else {
-                            nextType = 3;
-                            nextBigInteger = BigInteger.valueOf(nextLong);
-                            nextBigInteger = nextBigInteger.add(BigInteger.ONE);
-                        }
-                        break;
-                        
-                    default: // 3
-                        nextBigInteger = nextBigInteger.add(BigInteger.ONE);
-                    }
-                }
-                needInc = true;
-                return nextType == 1 ? new SimpleNumber(nextInt)
-                        : (nextType == 2 ? new SimpleNumber(nextLong)
-                        : new SimpleNumber(nextBigInteger)); 
-            }
-
-            @Override
-            public boolean hasNext() throws TemplateException {
-                return true;
-            }
-            
-        };
-        
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 b8354d7..7b9952e 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
@@ -20,13 +20,11 @@
 package org.apache.freemarker.core;
 
 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.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.util.BugException;
 import org.apache.freemarker.core.util.TemplateLanguageUtils;
 import org.apache.freemarker.core.util._StringUtils;
@@ -80,13 +78,11 @@ class MessageUtils {
         EXPECTED_TYPES_STRING_COERCABLE_TYPES_AND_TOM[i] = TemplateMarkupOutputModel.class;
     }
 
-    static final String SEQUENCE_OR_COLLECTION = "sequence or collection";
-    static final Class[] EXPECTED_TYPES_SEQUENCE_OR_COLLECTION = new Class[] {
-            TemplateSequenceModel.class, TemplateCollectionModel.class
-    };
+    static final String EXPECTED_TYPE_ITERABLE_DESC = "iterable (like a sequence)";
 
-    // Can't be instantiated
-    private MessageUtils() { }
+    private MessageUtils() {
+        // Not meant to be instantiated
+    }
 
     static String formatLocationForSimpleParsingError(String templateSourceOrLookupName, int line, int column) {
         return formatLocation("in", templateSourceOrLookupName, line, column);
@@ -271,6 +267,15 @@ class MessageUtils {
     }
 
     static TemplateException newUnexpectedOperandTypeException(
+            ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class<? extends TemplateModel> expectedType,
+            Object[] tips,
+            Environment env)
+            throws InvalidReferenceException {
+        return newUnexpectedOperandTypeException(
+                blamed, model, expectedTypesDesc, new Class[] { expectedType }, tips, env);
+    }
+
+    static TemplateException newUnexpectedOperandTypeException(
             ASTExpression blamed, TemplateModel model, String expectedTypesDesc, Class[] expectedTypes, Object[] tips,
             Environment env)
             throws InvalidReferenceException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollection.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollection.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollection.java
new file mode 100644
index 0000000..da90371
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollection.java
@@ -0,0 +1,55 @@
+/*
+ * 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 java.util.Collection;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
+/**
+ * A collection where each items is already a {@link TemplateModel}, so no {@link ObjectWrapper} need to be specified.
+ */
+class NativeCollection implements TemplateCollectionModel {
+
+    private final Collection<TemplateModel> collection;
+
+    public NativeCollection(Collection<TemplateModel> collection) {
+        this.collection = collection;
+    }
+
+    @Override
+    public int getCollectionSize() {
+        return collection.size();
+    }
+
+    @Override
+    public boolean isEmptyCollection() {
+        return collection.isEmpty();
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new NativeTemplateModelIterator(collection.iterator());
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollectionEx.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollectionEx.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollectionEx.java
deleted file mode 100644
index 119cae5..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeCollectionEx.java
+++ /dev/null
@@ -1,72 +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;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateCollectionModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-
-/**
- * A collection where each items is already a {@link TemplateModel}, so no {@link ObjectWrapper} need to be specified.
- */
-class NativeCollectionEx implements TemplateCollectionModelEx {
-
-    private final Collection<TemplateModel> collection;
-
-    public NativeCollectionEx(Collection<TemplateModel> collection) {
-        this.collection = collection;
-    }
-
-    @Override
-    public int size() {
-        return collection.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return collection.isEmpty();
-    }
-
-    @Override
-    public TemplateModelIterator iterator() throws TemplateException {
-        return new TemplateModelIterator() {
-
-            private final Iterator<TemplateModel> iterator = collection.iterator();
-
-            @Override
-            public TemplateModel next() throws TemplateException {
-                if (!iterator.hasNext()) {
-                    throw new TemplateException("The collection has no more items.");
-                }
-
-                return iterator.next();
-            }
-
-            @Override
-            public boolean hasNext() {
-                return iterator.hasNext();
-            }
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeHashEx2.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
index a7b2415..bb505c6 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeHashEx2.java
@@ -25,7 +25,7 @@ import java.util.LinkedHashMap;
 import java.util.Map;
 
 import org.apache.freemarker.core.model.ObjectWrapper;
-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.impl.SimpleString;
@@ -45,7 +45,7 @@ class NativeHashEx2 implements TemplateHashModelEx2, Serializable {
     }
 
     @Override
-    public int size() throws TemplateException {
+    public int getHashSize() throws TemplateException {
         return map.size();
     }
 
@@ -55,7 +55,7 @@ class NativeHashEx2 implements TemplateHashModelEx2, Serializable {
     }
 
     @Override
-    public boolean isEmpty() throws TemplateException {
+    public boolean isEmptyHash() throws TemplateException {
         return map.isEmpty();
     }
 
@@ -89,13 +89,13 @@ class NativeHashEx2 implements TemplateHashModelEx2, Serializable {
     }
 
     @Override
-    public TemplateCollectionModel keys() throws TemplateException {
-        return new NativeStringCollectionCollectionEx(map.keySet());
+    public TemplateIterableModel keys() throws TemplateException {
+        return new NativeStringCollectionCollection(map.keySet());
     }
 
     @Override
-    public TemplateCollectionModel values() throws TemplateException {
-        return new NativeCollectionEx(map.values());
+    public TemplateIterableModel values() throws TemplateException {
+        return new NativeCollection(map.values());
     }
 
     public TemplateModel put(String key, TemplateModel value) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 5a43ef6..b5febb8 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
@@ -25,6 +25,7 @@ import java.util.Collection;
 
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 
 /**
@@ -49,25 +50,53 @@ class NativeSequence implements TemplateSequenceModel, Serializable {
         this.items.addAll(items);
     }
 
-    public void add(TemplateModel tm) {
+    void add(TemplateModel tm) {
         items.add(tm);
     }
 
-    public void addAll(Collection<TemplateModel> items) {
+    void addAll(Collection<TemplateModel> items) {
         this.items.addAll(items);
     }
 
-    public void clear() {
+    void clear() {
         items.clear();
     }
 
     @Override
     public TemplateModel get(int index) throws TemplateException {
-        return items.get(index);
+        return index < items.size() && index >= 0 ? items.get(index) : null;
     }
 
     @Override
-    public int size() throws TemplateException {
+    public int getCollectionSize() throws TemplateException {
         return items.size();
     }
+
+    @Override
+    public boolean isEmptyCollection() throws TemplateException {
+        return items.isEmpty();
+    }
+
+    /**
+     * Do not call when you will still add items to the sequence!
+     *
+     * {@inheritDoc}
+     */
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new TemplateModelIterator() {
+            private int nextIndex = 0;
+            private final int size = items.size();
+
+            @Override
+            public TemplateModel next() throws TemplateException {
+                return items.get(nextIndex++);
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return nextIndex < size;
+            }
+        };
+    }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringArraySequence.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringArraySequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringArraySequence.java
index a74a63e..5aaf62b 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringArraySequence.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringArraySequence.java
@@ -21,6 +21,7 @@ package org.apache.freemarker.core;
 
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.impl.DefaultArrayAdapter;
 import org.apache.freemarker.core.model.impl.SimpleString;
@@ -41,12 +42,34 @@ class NativeStringArraySequence implements TemplateSequenceModel {
 
     @Override
     public TemplateModel get(int index) throws TemplateException {
-        return index < items.length ? new SimpleString(items[index]) : null;
+        return index < items.length && index >= 0 ? new SimpleString(items[index]) : null;
     }
 
     @Override
-    public int size() throws TemplateException {
+    public int getCollectionSize() throws TemplateException {
         return items.length;
     }
 
+    @Override
+    public boolean isEmptyCollection() throws TemplateException {
+        return items.length == 0;
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new TemplateModelIterator() {
+            private int nextIndex;
+
+            @Override
+            public TemplateModel next() throws TemplateException {
+                return new SimpleString(items[nextIndex++]);
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return nextIndex < items.length;
+            }
+        };
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollection.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollection.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollection.java
new file mode 100644
index 0000000..7793789
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollection.java
@@ -0,0 +1,74 @@
+/*
+ * 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 java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.model.ObjectWrapper;
+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.impl.DefaultNonListCollectionAdapter;
+import org.apache.freemarker.core.model.impl.SimpleString;
+
+/**
+ * Adapts (not copies) a {@link Collection} of {@link String}-s with on-the-fly wrapping of the items to {@link
+ * SimpleString}-s. The important difference to {@link DefaultNonListCollectionAdapter} is that it doesn't depend on an
+ * {@link ObjectWrapper}, which is needed to guarantee the behavior of some template language constructs. The important
+ * difference to {@link NativeCollection} is that it doesn't need upfront conversion to {@link TemplateModel}-s
+ * (performance).
+ */
+class NativeStringCollectionCollection implements TemplateCollectionModel {
+
+    private final Collection<String> collection;
+
+    public NativeStringCollectionCollection(Collection<String> collection) {
+        this.collection = collection;
+    }
+
+    @Override
+    public int getCollectionSize() {
+        return collection.size();
+    }
+
+    @Override
+    public boolean isEmptyCollection() {
+        return collection.isEmpty();
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new TemplateModelIterator() {
+
+            private final Iterator<String> iterator = collection.iterator();
+
+            @Override
+            public TemplateModel next() throws TemplateException {
+                return new SimpleString(iterator.next());
+            }
+
+            @Override
+            public boolean hasNext() {
+                return iterator.hasNext();
+            }
+        };
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollectionEx.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollectionEx.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollectionEx.java
deleted file mode 100644
index 78b2099..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringCollectionCollectionEx.java
+++ /dev/null
@@ -1,78 +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;
-
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateCollectionModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.model.impl.DefaultNonListCollectionAdapter;
-import org.apache.freemarker.core.model.impl.SimpleString;
-
-/**
- * Adapts (not copies) a {@link Collection} of {@link String}-s with on-the-fly wrapping of the items to {@link
- * SimpleString}-s. The important difference to {@link DefaultNonListCollectionAdapter} is that it doesn't depend on an
- * {@link ObjectWrapper}, which is needed to guarantee the behavior of some template language constructs. The important
- * difference to {@link NativeCollectionEx} is that it doesn't need upfront conversion to {@link TemplateModel}-s
- * (performance).
- */
-class NativeStringCollectionCollectionEx implements TemplateCollectionModelEx {
-
-    private final Collection<String> collection;
-
-    public NativeStringCollectionCollectionEx(Collection<String> collection) {
-        this.collection = collection;
-    }
-
-    @Override
-    public int size() {
-        return collection.size();
-    }
-
-    @Override
-    public boolean isEmpty() {
-        return collection.isEmpty();
-    }
-
-    @Override
-    public TemplateModelIterator iterator() throws TemplateException {
-        return new TemplateModelIterator() {
-
-            private final Iterator<String> iterator = collection.iterator();
-
-            @Override
-            public TemplateModel next() throws TemplateException {
-                if (!iterator.hasNext()) {
-                    throw new TemplateException("The collection has no more items.");
-                }
-
-                return new SimpleString(iterator.next());
-            }
-
-            @Override
-            public boolean hasNext() {
-                return iterator.hasNext();
-            }
-        };
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringListSequence.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringListSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringListSequence.java
index 8ab167e..4ddfe0d 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringListSequence.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeStringListSequence.java
@@ -23,6 +23,7 @@ import java.util.List;
 
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.impl.DefaultListAdapter;
 import org.apache.freemarker.core.model.impl.SimpleString;
@@ -44,12 +45,34 @@ class NativeStringListSequence implements TemplateSequenceModel {
 
     @Override
     public TemplateModel get(int index) throws TemplateException {
-        return index < items.size() ? new SimpleString(items.get(index)) : null;
+        return index < items.size() && index >= 0 ? new SimpleString(items.get(index)) : null;
     }
 
     @Override
-    public int size() throws TemplateException {
+    public int getCollectionSize() throws TemplateException {
         return items.size();
     }
 
+    @Override
+    public boolean isEmptyCollection() throws TemplateException {
+        return items.isEmpty();
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new TemplateModelIterator() {
+            private int nextIndex;
+
+            @Override
+            public TemplateModel next() throws TemplateException {
+                return new SimpleString(items.get(nextIndex++));
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return nextIndex < items.size();
+            }
+        };
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/NativeTemplateModelIterator.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeTemplateModelIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeTemplateModelIterator.java
new file mode 100644
index 0000000..c1c51a3
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeTemplateModelIterator.java
@@ -0,0 +1,44 @@
+/*
+ * 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 java.util.Iterator;
+
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
+class NativeTemplateModelIterator implements TemplateModelIterator {
+
+    private final Iterator<TemplateModel> iterator;
+
+    NativeTemplateModelIterator(Iterator<TemplateModel> iterator) {
+        this.iterator = iterator;
+    }
+
+    @Override
+    public TemplateModel next() throws TemplateException {
+        return iterator.next();
+    }
+
+    @Override
+    public boolean hasNext() {
+        return iterator.hasNext();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java
index 512102a..00be690 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/RangeModel.java
@@ -31,19 +31,20 @@ abstract class RangeModel implements TemplateSequenceModel, java.io.Serializable
         this.begin = begin;
     }
 
-    final int getBegining() {
+    final int getBeginning() {
         return begin;
     }
     
     @Override
-    final public TemplateModel get(int index) throws TemplateException {
-        if (index < 0 || index >= size()) {
-            throw new TemplateException("Range item index ", Integer.valueOf(index), " is out of bounds.");
-        }
-        long value = begin + getStep() * (long) index;
+    public final TemplateModel get(int index) throws TemplateException {
+        return index < getCollectionSize() && index >= 0 ? uncheckedGet(index) : null;
+    }
+
+    protected final TemplateModel uncheckedGet(long index) {
+        long value = begin + getStep() * index;
         return value <= Integer.MAX_VALUE ? new SimpleNumber((int) value) : new SimpleNumber(value);
     }
-    
+
     /**
      * @return {@code 1} or {@code -1}; other return values need not be properly handled until FTL supports other steps.
      */
@@ -52,7 +53,5 @@ abstract class RangeModel implements TemplateSequenceModel, java.io.Serializable
     abstract boolean isRightUnbounded();
     
     abstract boolean isRightAdaptive();
-    
-    abstract boolean isAffactedByStringSlicingBug();
 
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java
index e135d18..04ab6bc 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/RightUnboundedRangeModel.java
@@ -19,8 +19,18 @@
 
 package org.apache.freemarker.core;
 
-abstract class RightUnboundedRangeModel extends RangeModel {
-    
+import java.math.BigInteger;
+
+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.impl.SimpleNumber;
+
+/**
+ * This is the model used for right-unbounded ranges
+ */
+final class RightUnboundedRangeModel extends RangeModel implements TemplateIterableModel {
+
     RightUnboundedRangeModel(int begin) {
         super(begin);
     }
@@ -34,15 +44,71 @@ abstract class RightUnboundedRangeModel extends RangeModel {
     final boolean isRightUnbounded() {
         return true;
     }
-    
+
     @Override
     final boolean isRightAdaptive() {
         return true;
     }
 
     @Override
-    final boolean isAffactedByStringSlicingBug() {
+    public int getCollectionSize() throws TemplateException {
+        return Integer.MAX_VALUE;
+    }
+
+    @Override
+    public boolean isEmptyCollection() throws TemplateException {
         return false;
     }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new TemplateModelIterator() {
+            boolean needInc;
+            int nextType = 1;
+            int nextInt = getBeginning();
+            long nextLong;
+            BigInteger nextBigInteger;
+
+            @Override
+            public TemplateModel next() throws TemplateException {
+                if (needInc) {
+                    switch (nextType) {
+                    case 1:
+                        if (nextInt < Integer.MAX_VALUE) {
+                            nextInt++;
+                        } else {
+                            nextType = 2;
+                            nextLong = nextInt + 1L;
+                        }
+                        break;
+                        
+                    case 2:
+                        if (nextLong < Long.MAX_VALUE) {
+                            nextLong++;
+                        } else {
+                            nextType = 3;
+                            nextBigInteger = BigInteger.valueOf(nextLong);
+                            nextBigInteger = nextBigInteger.add(BigInteger.ONE);
+                        }
+                        break;
+                        
+                    default: // 3
+                        nextBigInteger = nextBigInteger.add(BigInteger.ONE);
+                    }
+                }
+                needInc = true;
+                return nextType == 1 ? new SimpleNumber(nextInt)
+                        : (nextType == 2 ? new SimpleNumber(nextLong)
+                        : new SimpleNumber(nextBigInteger)); 
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return true;
+            }
+            
+        };
+        
+    }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
index a8e2ed1..30b869c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/_EvalUtils.java
@@ -26,13 +26,12 @@ import java.util.Date;
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
 import org.apache.freemarker.core.arithmetic.impl.BigDecimalArithmeticEngine;
 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.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.outputformat.MarkupOutputFormat;
 import org.apache.freemarker.core.util.BugException;
 import org.apache.freemarker.core.util._ClassUtils;
@@ -338,7 +337,7 @@ public class _EvalUtils {
      * {@link TemplateValueFormat} involved produces.
      * 
      * @param seqTip
-     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     *            Tip to display if the value type is not coercable, but it's iterable.
      * 
      * @return Never {@code null}
      */
@@ -384,7 +383,7 @@ public class _EvalUtils {
      * if the result is markup. This is what you normally use where markup results can't be used.
      *
      * @param seqTip
-     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     *            Tip to display if the value type is not coercable, but it's iterable.
      * 
      * @return Never {@code null}
      */
@@ -417,7 +416,7 @@ public class _EvalUtils {
      * markup. This should be used rarely, where the user clearly intend to use the plain text variant of the format.
      * 
      * @param seqTip
-     *            Tip to display if the value type is not coercable, but it's sequence or collection.
+     *            Tip to display if the value type is not coercable, but it's iterable.
      * 
      * @return Never {@code null}
      */
@@ -469,7 +468,7 @@ public class _EvalUtils {
                     exp, tm,
                     supportsTOM ? STRING_COERCABLE_TYPES_OR_TOM_DESC : STRING_COERCABLE_TYPES_DESC,
                     supportsTOM ? EXPECTED_TYPES_STRING_COERCABLE_TYPES_AND_TOM : EXPECTED_TYPES_STRING_COERCABLE,
-                    seqHint != null && (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel)
+                    seqHint != null && tm instanceof TemplateIterableModel
                             ? new Object[] { seqHint }
                             : null,
                     env);