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

[2/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/model/impl/DefaultObjectWrapper.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
index ca06017..2d78119 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapper.java
@@ -57,11 +57,13 @@ import org.apache.freemarker.core.model.ObjectWrappingException;
 import org.apache.freemarker.core.model.RichObjectWrapper;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 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.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelAdapter;
+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;
@@ -511,7 +513,7 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
      * {@link TemplateBooleanModel} instances into a Boolean, arbitrary
      * {@link TemplateHashModel} instances into a Map, arbitrary
      * {@link TemplateSequenceModel} into a List, and arbitrary
-     * {@link TemplateCollectionModel} into a Set. All other objects are
+     * {@link TemplateIterableModel} into a Set. All other objects are
      * returned unchanged.
      * @throws TemplateException if an attempted unwrapping fails.
      */
@@ -649,29 +651,41 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
 
             if (Map.class == targetClass) {
                 if (model instanceof TemplateHashModel) {
-                    return new HashAdapter((TemplateHashModel) model, this);
+                    return new TemplateHashModelAdapter((TemplateHashModel) model, this);
                 }
             }
 
             if (List.class == targetClass) {
                 if (model instanceof TemplateSequenceModel) {
-                    return new SequenceAdapter((TemplateSequenceModel) model, this);
+                    return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this);
                 }
             }
 
             if (Set.class == targetClass) {
                 if (model instanceof TemplateCollectionModel) {
-                    return new SetAdapter((TemplateCollectionModel) model, this);
+                    return new TemplateSetModelAdapter((TemplateCollectionModel) model, this);
                 }
             }
 
-            if (Collection.class == targetClass || Iterable.class == targetClass) {
+            if (Collection.class == targetClass) {
+                if (model instanceof TemplateSequenceModel) {
+                    return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this);
+                }
                 if (model instanceof TemplateCollectionModel) {
-                    return new CollectionAdapter((TemplateCollectionModel) model,
-                            this);
+                    return new TemplateCollectionModelAdapter((TemplateCollectionModel) model, this);
                 }
+            }
+
+            if (Iterable.class == targetClass) {
                 if (model instanceof TemplateSequenceModel) {
-                    return new SequenceAdapter((TemplateSequenceModel) model, this);
+                    return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this);
+                }
+                if (model instanceof TemplateCollectionModel) {
+                    return new TemplateCollectionModelAdapter((TemplateCollectionModel) model, this);
+                }
+                if (model instanceof TemplateIterableModel) {
+                    return new TemplateIterableModelAdapter((TemplateIterableModel) model,
+                            this);
                 }
             }
 
@@ -753,23 +767,23 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             }
             if ((itf == 0 || (itf & TypeFlags.ACCEPTS_MAP) != 0)
                     && model instanceof TemplateHashModel
-                    && (itf != 0 || targetClass.isAssignableFrom(HashAdapter.class))) {
-                return new HashAdapter((TemplateHashModel) model, this);
+                    && (itf != 0 || targetClass.isAssignableFrom(TemplateHashModelAdapter.class))) {
+                return new TemplateHashModelAdapter((TemplateHashModel) model, this);
             }
             if ((itf == 0 || (itf & TypeFlags.ACCEPTS_LIST) != 0)
                     && model instanceof TemplateSequenceModel
-                    && (itf != 0 || targetClass.isAssignableFrom(SequenceAdapter.class))) {
-                return new SequenceAdapter((TemplateSequenceModel) model, this);
+                    && (itf != 0 || targetClass.isAssignableFrom(TemplateSequenceModelAdapter.class))) {
+                return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this);
             }
             if ((itf == 0 || (itf & TypeFlags.ACCEPTS_SET) != 0)
                     && model instanceof TemplateCollectionModel
-                    && (itf != 0 || targetClass.isAssignableFrom(SetAdapter.class))) {
-                return new SetAdapter((TemplateCollectionModel) model, this);
+                    && (itf != 0 || targetClass.isAssignableFrom(TemplateSetModelAdapter.class))) {
+                return new TemplateSetModelAdapter((TemplateCollectionModel) model, this);
             }
 
             if ((itf & TypeFlags.ACCEPTS_ARRAY) != 0
                     && model instanceof TemplateSequenceModel) {
-                return new SequenceAdapter((TemplateSequenceModel) model, this);
+                return new TemplateSequenceModelAdapter((TemplateSequenceModel) model, this);
             }
 
             if (itf == 0) {
@@ -805,12 +819,13 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
             recursionStops = new IdentityHashMap<>();
         }
         Class<?> componentType = arrayClass.getComponentType();
-        Object array = Array.newInstance(componentType, seq.size());
+        final int size = seq.getCollectionSize();
+        Object array = Array.newInstance(componentType, size);
         recursionStops.put(seq, array);
         try {
-            final int size = seq.size();
-            for (int i = 0; i < size; i++) {
-                final TemplateModel seqItem = seq.get(i);
+            TemplateModelIterator iter = seq.iterator();
+            for (int idx = 0; idx < size; idx++) {
+                final TemplateModel seqItem = iter.next();
                 Object val = tryUnwrapTo(seqItem, componentType, 0, recursionStops);
                 if (val == ObjectWrapperAndUnwrapper.CANT_UNWRAP_TO_TARGET_CLASS) {
                     if (tryOnly) {
@@ -819,12 +834,12 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
                         throw new TemplateException(
                                 "Failed to convert ",  new _DelayedTemplateLanguageTypeDescription(seq),
                                 " object to ", new _DelayedShortClassName(array.getClass()),
-                                ": Problematic sequence item at index ", Integer.valueOf(i) ," with value type: ",
+                                ": Problematic sequence item at index ", Integer.valueOf(idx) ," with value type: ",
                                 new _DelayedTemplateLanguageTypeDescription(seqItem));
                     }
 
                 }
-                Array.set(array, i, val);
+                Array.set(array, idx, val);
             }
         } finally {
             recursionStops.remove(seq);
@@ -834,9 +849,9 @@ public class DefaultObjectWrapper implements RichObjectWrapper {
 
     Object listToArray(List<?> list, Class<?> arrayClass, Map<Object, Object> recursionStops)
             throws TemplateException {
-        if (list instanceof SequenceAdapter) {
+        if (list instanceof TemplateSequenceModelAdapter) {
             return unwrapSequenceToArray(
-                    ((SequenceAdapter) list).getTemplateSequenceModel(),
+                    ((TemplateSequenceModelAdapter) list).getTemplateSequenceModel(),
                     arrayClass, false,
                     recursionStops);
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java
deleted file mode 100644
index c87f3f7..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultUnassignableIteratorAdapter.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.freemarker.core.model.impl;
-
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.ObjectWrapper;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-
-/**
- * As opposed to {@link DefaultIteratorAdapter}, this simpler {@link Iterator} adapter is used in situations where the
- * {@link TemplateModelIterator} won't be assigned to FreeMarker template variables, only used internally by
- * {@code #list} or custom Java code. Because of that, it doesn't have to handle the situation where the user tries to
- * iterate over the same value twice.
- */
-class DefaultUnassignableIteratorAdapter implements TemplateModelIterator {
-
-    private final Iterator<?> it;
-    private final ObjectWrapper wrapper;
-
-    DefaultUnassignableIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) {
-        this.it = it;
-        this.wrapper = wrapper;
-    }
-
-    @Override
-    public TemplateModel next() throws TemplateException {
-        try {
-            return wrapper.wrap(it.next());
-        } catch (NoSuchElementException e) {
-            throw new TemplateException("The collection has no more items.", e);
-        }
-    }
-
-    @Override
-    public boolean hasNext() throws TemplateException {
-        return it.hasNext();
-    }
-
-}
\ 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/model/impl/HashAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
deleted file mode 100644
index d829f6b..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/HashAdapter.java
+++ /dev/null
@@ -1,181 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import java.util.AbstractMap;
-import java.util.AbstractSet;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.Set;
-
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateHashModel;
-import org.apache.freemarker.core.model.TemplateHashModelEx;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelIterator;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-
-/**
- * Adapts a {@link TemplateHashModel} to a {@link Map}.
- */
-class HashAdapter extends AbstractMap implements TemplateModelAdapter {
-    private final DefaultObjectWrapper wrapper;
-    private final TemplateHashModel model;
-    private Set entrySet;
-    
-    HashAdapter(TemplateHashModel model, DefaultObjectWrapper wrapper) {
-        this.model = model;
-        this.wrapper = wrapper;
-    }
-    
-    @Override
-    public TemplateModel getTemplateModel() {
-        return model;
-    }
-    
-    @Override
-    public boolean isEmpty() {
-        try {
-            return model.isEmpty();
-        } catch (TemplateException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-    
-    @Override
-    public Object get(Object key) {
-        try {
-            return wrapper.unwrap(model.get(String.valueOf(key)));
-        } catch (TemplateException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-
-    @Override
-    public boolean containsKey(Object key) {
-        // A quick check that doesn't require TemplateHashModelEx 
-        if (get(key) != null) {
-            return true;
-        }
-        return super.containsKey(key);
-    }
-    
-    @Override
-    public Set entrySet() {
-        if (entrySet != null) {
-            return entrySet;
-        }
-        return entrySet = new AbstractSet() {
-            @Override
-            public Iterator iterator() {
-                final TemplateModelIterator i;
-                try {
-                     i = getModelEx().keys().iterator();
-                } catch (TemplateException e) {
-                    throw new UndeclaredThrowableException(e);
-                }
-                return new Iterator() {
-                    @Override
-                    public boolean hasNext() {
-                        try {
-                            return i.hasNext();
-                        } catch (TemplateException e) {
-                            throw new UndeclaredThrowableException(e);
-                        }
-                    }
-                    
-                    @Override
-                    public Object next() {
-                        final Object key;
-                        try {
-                            key = wrapper.unwrap(i.next());
-                        } catch (TemplateException e) {
-                            throw new UndeclaredThrowableException(e);
-                        }
-                        return new Map.Entry() {
-                            @Override
-                            public Object getKey() {
-                                return key;
-                            }
-                            
-                            @Override
-                            public Object getValue() {
-                                return get(key);
-                            }
-                            
-                            @Override
-                            public Object setValue(Object value) {
-                                throw new UnsupportedOperationException();
-                            }
-                            
-                            @Override
-                            public boolean equals(Object o) {
-                                if (!(o instanceof Map.Entry))
-                                    return false;
-                                Map.Entry e = (Map.Entry) o;
-                                Object k1 = getKey();
-                                Object k2 = e.getKey();
-                                if (k1 == k2 || (k1 != null && k1.equals(k2))) {
-                                    Object v1 = getValue();
-                                    Object v2 = e.getValue();
-                                    if (v1 == v2 || (v1 != null && v1.equals(v2))) 
-                                        return true;
-                                }
-                                return false;
-                            }
-                        
-                            @Override
-                            public int hashCode() {
-                                Object value = getValue();
-                                return (key == null ? 0 : key.hashCode()) ^
-                                       (value == null ? 0 : value.hashCode());
-                            }
-                        };
-                    }
-                    
-                    @Override
-                    public void remove() {
-                        throw new UnsupportedOperationException();
-                    }
-                };
-            }
-            
-            @Override
-            public int size() {
-                try {
-                    return getModelEx().size();
-                } catch (TemplateException e) {
-                    throw new UndeclaredThrowableException(e);
-                }
-            }
-        };
-    }
-    
-    private TemplateHashModelEx getModelEx() {
-        if (model instanceof TemplateHashModelEx) {
-            return ((TemplateHashModelEx) model);
-        }
-        throw new UnsupportedOperationException(
-                "Operation supported only on TemplateHashModelEx. " + 
-                model.getClass().getName() + " does not implement it though.");
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java
new file mode 100644
index 0000000..eb51098
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java
@@ -0,0 +1,82 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.ArrayList;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateIterableModel;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+
+/**
+ * Add sequence capabilities to an existing iterable. Used by ?keys and ?values built-ins.
+ */
+// TODO [FM3] Maybe ?keys/?values should just return a TemplateCollectionModel
+final public class IterableAndSequence implements TemplateSequenceModel {
+    private TemplateIterableModel iterable;
+    private ArrayList<TemplateModel> data;
+
+    public IterableAndSequence(TemplateIterableModel iterable) {
+        this.iterable = iterable;
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return iterable.iterator();
+    }
+
+    @Override
+    public TemplateModel get(int i) throws TemplateException {
+        initSequence();
+        return i < data.size() && i >= 0 ? data.get(i) : null;
+    }
+
+    @Override
+    public int getCollectionSize() throws TemplateException {
+        if (iterable instanceof TemplateCollectionModel) {
+            return ((TemplateCollectionModel) iterable).getCollectionSize();
+        } else {
+            initSequence();
+            return data.size();
+        }
+    }
+
+    @Override
+    public boolean isEmptyCollection() throws TemplateException {
+        if (iterable instanceof TemplateCollectionModel) {
+            return ((TemplateCollectionModel) iterable).isEmptyCollection();
+        } else {
+            return iterable.iterator().hasNext();
+        }
+    }
+
+    private void initSequence() throws TemplateException {
+        if (data == null) {
+            data = new ArrayList<>();
+            TemplateModelIterator it = iterable.iterator();
+            while (it.hasNext()) {
+                data.add(it.next());
+            }
+        }
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java
new file mode 100644
index 0000000..df31393
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IteratorToTemplateModelIteratorAdapter.java
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Iterator;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.ObjectWrapper;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
+/**
+ * Unlike {@link DefaultIteratorAdapter}, this doesn't adapt to some {@link TemplateModel}, but to {@link
+ * TemplateModelIterator}.
+ */
+class IteratorToTemplateModelIteratorAdapter implements TemplateModelIterator {
+
+    private final Iterator<?> it;
+    private final ObjectWrapper wrapper;
+
+    IteratorToTemplateModelIteratorAdapter(Iterator<?> it, ObjectWrapper wrapper) {
+        this.it = it;
+        this.wrapper = wrapper;
+    }
+
+    @Override
+    public TemplateModel next() throws TemplateException {
+        return wrapper.wrap(it.next());
+    }
+
+    @Override
+    public boolean hasNext() throws TemplateException {
+        return it.hasNext();
+    }
+
+}
\ 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/model/impl/ResourceBundleModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
index 037ff15..9f12c05 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ResourceBundleModel.java
@@ -79,13 +79,13 @@ public class ResourceBundleModel extends BeanModel implements TemplateFunctionMo
      * Returns true if this bundle contains no objects.
      */
     @Override
-    public boolean isEmpty() {
+    public boolean isEmptyHash() {
         return !((ResourceBundle) object).getKeys().hasMoreElements() &&
-            super.isEmpty();
+            super.isEmptyHash();
     }
 
     @Override
-    public int size() {
+    public int getHashSize() {
         return keySet().size();
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java
deleted file mode 100644
index b7a9136..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceAdapter.java
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import java.util.AbstractList;
-
-import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateSequenceModel;
-import org.apache.freemarker.core.util.UndeclaredThrowableException;
-
-/**
- */
-class SequenceAdapter extends AbstractList implements TemplateModelAdapter {
-    private final DefaultObjectWrapper wrapper;
-    private final TemplateSequenceModel model;
-    
-    SequenceAdapter(TemplateSequenceModel model, DefaultObjectWrapper wrapper) {
-        this.model = model;
-        this.wrapper = wrapper;
-    }
-    
-    @Override
-    public TemplateModel getTemplateModel() {
-        return model;
-    }
-    
-    @Override
-    public int size() {
-        try {
-            return model.size();
-        } catch (TemplateException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-    
-    @Override
-    public Object get(int index) {
-        try {
-            return wrapper.unwrap(model.get(index));
-        } catch (TemplateException e) {
-            throw new UndeclaredThrowableException(e);
-        }
-    }
-    
-    public TemplateSequenceModel getTemplateSequenceModel() {
-        return model;
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java
new file mode 100644
index 0000000..ede0bac
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SequenceTemplateModelIterator.java
@@ -0,0 +1,63 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.List;
+
+import org.apache.freemarker.core.TemplateException;
+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.TemplateSequenceModel;
+
+/**
+ * {@link TemplateIterableModel} implementation that can iterate through a {@link TemplateSequenceModel} be going
+ * through the valid indexes and calling {@link TemplateSequenceModel#get(int)} for each. This should only be used if
+ * you can be sure that {@link TemplateSequenceModel#get(int)} and {@link TemplateSequenceModel#getCollectionSize()} is efficient.
+ * ({@link TemplateSequenceModel} doesn't require that to be efficient, as listing the contents should be done with with
+ * {@link TemplateIterableModel#iterator()}).
+ * <p>
+ * This class assumes that the sequence doesn't change during iteration (as it's technically impossible to detect if a
+ * generic {@link TemplateSequenceModel} was changed, similarly as it's impossible for a generic {@link List}). The
+ * index range is decided when the instance is created, and it simply iterates through the predefined index range. Thus
+ * for example, if the sequence length decreases after that, it will return {@code null}-s (means missing element) at
+ * the end, for the indexes that are now out of bounds.
+ */
+public class SequenceTemplateModelIterator implements TemplateModelIterator {
+
+    private final TemplateSequenceModel sequence;
+    private final int size;
+    private int nextIndex = 0;
+
+    public SequenceTemplateModelIterator(TemplateSequenceModel sequence) throws TemplateException {
+        this.sequence = sequence;
+        this.size = sequence.getCollectionSize();
+    }
+
+    @Override
+    public TemplateModel next() throws TemplateException {
+        return sequence.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/model/impl/SetAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java
deleted file mode 100644
index 0975ac4..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SetAdapter.java
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import java.util.Set;
-
-import org.apache.freemarker.core.model.TemplateCollectionModel;
-
-/**
- */
-class SetAdapter extends CollectionAdapter implements Set {
-    SetAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) {
-        super(model, wrapper);
-    }
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java
deleted file mode 100644
index 012beb6..0000000
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleCollection.java
+++ /dev/null
@@ -1,138 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *   http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-package org.apache.freemarker.core.model.impl;
-
-import java.io.Serializable;
-import java.util.Collection;
-import java.util.Iterator;
-
-import org.apache.freemarker.core.TemplateException;
-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.WrappingTemplateModel;
-
-/**
- * A simple implementation of {@link TemplateCollectionModel}.
- * It's able to wrap <tt>java.util.Iterator</tt>-s and <tt>java.util.Collection</tt>-s.
- * If you wrap an <tt>Iterator</tt>, the variable can be &lt;#list&gt;-ed only once!
- *
- * <p>Consider using {@link SimpleSequence} instead of this class if you want to wrap <tt>Iterator</tt>s.
- * <tt>SimpleSequence</tt> will read all elements of the <tt>Iterator</tt>, and store them in a <tt>List</tt>
- * (this may cause too high resource consumption in some applications), so you can list the variable
- * for unlimited times. Also, if you want to wrap <tt>Collection</tt>s, and then list the resulting
- * variable for many times, <tt>SimpleSequence</tt> may gives better performance, as the
- * wrapping of non-<tt>TemplateModel</tt> objects happens only once.
- *
- * <p>This class is thread-safe. The returned {@link TemplateModelIterator}-s
- * are <em>not</em> thread-safe.
- */
-public class SimpleCollection extends WrappingTemplateModel
-implements TemplateCollectionModel, Serializable {
-    
-    private boolean iteratorOwned;
-    private final Iterator iterator;
-    private final Collection collection;
-
-    public SimpleCollection(Iterator iterator, ObjectWrapper wrapper) {
-        super(wrapper);
-        this.iterator = iterator;
-        collection = null;
-    }
-
-    public SimpleCollection(Collection collection, ObjectWrapper wrapper) {
-        super(wrapper);
-        this.collection = collection;
-        iterator = null;
-    }
-
-    /**
-     * Retrieves a template model iterator that is used to iterate over the elements in this collection.
-     *  
-     * <p>When you wrap an <tt>Iterator</tt> and you get <tt>TemplateModelIterator</tt> for multiple times,
-     * only one of the returned <tt>TemplateModelIterator</tt> instances can be really used. When you have called a
-     * method of a <tt>TemplateModelIterator</tt> instance, all other instance will throw a
-     * {@link TemplateException} when you try to call their methods, since the wrapped <tt>Iterator</tt>
-     * can't return the first element anymore.
-     */
-    @Override
-    public TemplateModelIterator iterator() {
-        return iterator != null
-                ? new SimpleTemplateModelIterator(iterator, false)
-                : new SimpleTemplateModelIterator(collection.iterator(), true);
-    }
-    
-    /**
-     * Wraps an {@link Iterator}; not thread-safe. The encapsulated {@link Iterator} may be accessible from multiple
-     * threads (as multiple {@link SimpleTemplateModelIterator} instance can wrap the same {@link Iterator} instance),
-     * but if the {@link Iterator} was marked in the constructor as shared, the first thread which uses the
-     * {@link Iterator} will monopolize that.
-     */
-    private class SimpleTemplateModelIterator implements TemplateModelIterator {
-        
-        private final Iterator iterator;
-        private boolean iteratorOwnedByMe;
-            
-        SimpleTemplateModelIterator(Iterator iterator, boolean iteratorOwnedByMe) {
-            this.iterator = iterator;
-            this.iteratorOwnedByMe = iteratorOwnedByMe;
-        }
-
-        @Override
-        public TemplateModel next() throws TemplateException {
-            if (!iteratorOwnedByMe) { 
-                synchronized (SimpleCollection.this) {
-                    checkIteratorNotOwned();
-                    iteratorOwned = true;
-                    iteratorOwnedByMe = true;
-                }
-            }
-            
-            if (!iterator.hasNext()) {
-                throw new TemplateException("The collection has no more items.");
-            }
-            
-            Object value  = iterator.next();
-            return value instanceof TemplateModel ? (TemplateModel) value : wrap(value);
-        }
-
-        @Override
-        public boolean hasNext() throws TemplateException {
-            // Calling hasNext may looks safe, but I have met sync. problems.
-            if (!iteratorOwnedByMe) {
-                synchronized (SimpleCollection.this) {
-                    checkIteratorNotOwned();
-                }
-            }
-            
-            return iterator.hasNext();
-        }
-        
-        private void checkIteratorNotOwned() throws TemplateException {
-            if (iteratorOwned) {
-                throw new TemplateException(
-                        "This collection value wraps a java.util.Iterator, thus it can be listed only once.");
-            }
-        }
-        
-    }
-    
-}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
index df2f6ae..e2c867e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleHash.java
@@ -30,7 +30,7 @@ import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core._DelayedJQuote;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
-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.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
@@ -267,23 +267,23 @@ public class SimpleHash extends WrappingTemplateModel implements TemplateHashMod
     }
 
     @Override
-    public int size() {
+    public int getHashSize() {
         return map.size();
     }
 
     @Override
-    public boolean isEmpty() {
+    public boolean isEmptyHash() {
         return map == null || map.isEmpty();
     }
 
     @Override
-    public TemplateCollectionModel keys() {
-        return new SimpleCollection(map.keySet(), getObjectWrapper());
+    public TemplateIterableModel keys() {
+        return new SimpleIterable(map.keySet(), getObjectWrapper());
     }
 
     @Override
-    public TemplateCollectionModel values() {
-        return new SimpleCollection(map.values(), getObjectWrapper());
+    public TemplateIterableModel values() {
+        return new SimpleIterable(map.values(), getObjectWrapper());
     }
 
     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java
new file mode 100644
index 0000000..1856535
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleIterable.java
@@ -0,0 +1,133 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.io.Serializable;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.ObjectWrapper;
+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.WrappingTemplateModel;
+
+/**
+ * A simple implementation of {@link TemplateIterableModel}.
+ * It's able to wrap <tt>java.util.Iterator</tt>-s and <tt>java.util.Collection</tt>-s.
+ * If you wrap an <tt>Iterator</tt>, the variable can be &lt;#list&gt;-ed only once!
+ *
+ * <p>Consider using {@link SimpleSequence} instead of this class if you want to wrap <tt>Iterator</tt>s.
+ * <tt>SimpleSequence</tt> will read all elements of the <tt>Iterator</tt>, and store them in a <tt>List</tt>
+ * (this may cause too high resource consumption in some applications), so you can list the variable
+ * for unlimited times. Also, if you want to wrap <tt>Collection</tt>s, and then list the resulting
+ * variable for many times, <tt>SimpleSequence</tt> may gives better performance, as the
+ * wrapping of non-<tt>TemplateModel</tt> objects happens only once.
+ *
+ * <p>This class is thread-safe. The returned {@link TemplateModelIterator}-s
+ * are <em>not</em> thread-safe.
+ */
+public class SimpleIterable extends WrappingTemplateModel
+implements TemplateIterableModel, Serializable {
+    
+    private boolean iteratorOwned;
+    private final Iterator iterator;
+    private final Iterable iterable;
+
+    public SimpleIterable(Iterator iterator, ObjectWrapper wrapper) {
+        super(wrapper);
+        this.iterator = iterator;
+        iterable = null;
+    }
+
+    public SimpleIterable(Iterable iterable, ObjectWrapper wrapper) {
+        super(wrapper);
+        this.iterable = iterable;
+        iterator = null;
+    }
+
+    /**
+     * Retrieves a template model iterator that is used to iterate over the elements in this iterable.
+     *  
+     * <p>When you wrap an <tt>Iterator</tt> and you get <tt>TemplateModelIterator</tt> for multiple times,
+     * only one of the returned <tt>TemplateModelIterator</tt> instances can be really used. When you have called a
+     * method of a <tt>TemplateModelIterator</tt> instance, all other instance will throw a
+     * {@link TemplateException} when you try to call their methods, since the wrapped <tt>Iterator</tt>
+     * can't return the first element anymore.
+     */
+    @Override
+    public TemplateModelIterator iterator() {
+        return iterator != null
+                ? new SimpleTemplateModelIterator(iterator, false)
+                : new SimpleTemplateModelIterator(iterable.iterator(), true);
+    }
+    
+    /**
+     * Wraps an {@link Iterator}; not thread-safe. The encapsulated {@link Iterator} may be accessible from multiple
+     * threads (as multiple {@link SimpleTemplateModelIterator} instance can wrap the same {@link Iterator} instance),
+     * but if the {@link Iterator} was marked in the constructor as shared, the first thread which uses the
+     * {@link Iterator} will monopolize that.
+     */
+    private class SimpleTemplateModelIterator implements TemplateModelIterator {
+        
+        private final Iterator iterator;
+        private boolean iteratorOwnedByMe;
+            
+        SimpleTemplateModelIterator(Iterator iterator, boolean iteratorOwnedByMe) {
+            this.iterator = iterator;
+            this.iteratorOwnedByMe = iteratorOwnedByMe;
+        }
+
+        @Override
+        public TemplateModel next() throws TemplateException {
+            if (!iteratorOwnedByMe) { 
+                synchronized (SimpleIterable.this) {
+                    checkIteratorNotOwned();
+                    iteratorOwned = true;
+                    iteratorOwnedByMe = true;
+                }
+            }
+
+            Object value  = iterator.next();
+            return value instanceof TemplateModel ? (TemplateModel) value : wrap(value);
+        }
+
+        @Override
+        public boolean hasNext() throws TemplateException {
+            // Calling hasNext may looks safe, but I have met sync. problems.
+            if (!iteratorOwnedByMe) {
+                synchronized (SimpleIterable.this) {
+                    checkIteratorNotOwned();
+                }
+            }
+            
+            return iterator.hasNext();
+        }
+        
+        private void checkIteratorNotOwned() throws TemplateException {
+            if (iteratorOwned) {
+                throw new TemplateException(
+                        "This value wraps a java.util.Iterator, thus it can be listed only once.");
+            }
+        }
+        
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
index e0e0397..851147a 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SimpleSequence.java
@@ -26,6 +26,7 @@ import java.util.List;
 import org.apache.freemarker.core.TemplateException;
 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.WrappingTemplateModel;
 
@@ -133,25 +134,35 @@ public class SimpleSequence extends WrappingTemplateModel implements TemplateSeq
      */
     @Override
     public TemplateModel get(int index) throws TemplateException {
-        try {
-            Object value = list.get(index);
-            if (value instanceof TemplateModel) {
-                return (TemplateModel) value;
-            }
-            TemplateModel tm = wrap(value);
-            list.set(index, tm);
-            return tm;
-        } catch (IndexOutOfBoundsException e) {
+        if (index >= list.size() || index < 0) {
             return null;
         }
+
+        Object value = list.get(index);
+        if (value instanceof TemplateModel) {
+            return (TemplateModel) value;
+        }
+        TemplateModel tm = wrap(value);
+        list.set(index, tm);
+        return tm;
     }
 
     @Override
-    public int size() {
+    public int getCollectionSize() {
         return list.size();
     }
 
     @Override
+    public boolean isEmptyCollection() throws TemplateException {
+        return list.isEmpty();
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new SequenceTemplateModelIterator(this);
+    }
+
+    @Override
     public String toString() {
         return list.toString();
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java
new file mode 100644
index 0000000..7b8461b
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/SingleItemTemplateModelIterator.java
@@ -0,0 +1,48 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+
+/**
+ * {@link TemplateModelIterator} for the special case when you have exactly one element.
+ */
+public class SingleItemTemplateModelIterator implements TemplateModelIterator {
+
+    private final TemplateModel element;
+    private boolean hasNext;
+
+    public SingleItemTemplateModelIterator(TemplateModel element) {
+        this.element = element;
+    }
+
+    @Override
+    public TemplateModel next() throws TemplateException {
+        hasNext = false;
+        return element;
+    }
+
+    @Override
+    public boolean hasNext() throws TemplateException {
+        return hasNext;
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
index 12ecbc3..1ea6027 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/StaticModel.java
@@ -27,7 +27,7 @@ import java.util.Iterator;
 import java.util.Map;
 
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
@@ -84,23 +84,23 @@ final class StaticModel implements TemplateHashModelEx {
      * field or method in the underlying class.
      */
     @Override
-    public boolean isEmpty() {
+    public boolean isEmptyHash() {
         return map.isEmpty();
     }
 
     @Override
-    public int size() {
+    public int getHashSize() {
         return map.size();
     }
     
     @Override
-    public TemplateCollectionModel keys() throws TemplateException {
-        return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.keySet());
+    public TemplateIterableModel keys() throws TemplateException {
+        return (TemplateIterableModel) wrapper.getOuterIdentity().wrap(map.keySet());
     }
     
     @Override
-    public TemplateCollectionModel values() throws TemplateException {
-        return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.values());
+    public TemplateIterableModel values() throws TemplateException {
+        return (TemplateIterableModel) wrapper.getOuterIdentity().wrap(map.values());
     }
 
     private void populate() throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java
new file mode 100644
index 0000000..91a38ed
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateCollectionModelAdapter.java
@@ -0,0 +1,77 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.AbstractCollection;
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateIterableModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ * Adapts a {@link TemplateIterableModel} to  {@link Collection}.
+ */
+class TemplateCollectionModelAdapter<T> extends AbstractCollection<T> implements TemplateModelAdapter {
+    private final DefaultObjectWrapper wrapper;
+    private final TemplateCollectionModel model;
+
+    TemplateCollectionModelAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) {
+        this.model = model;
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    public TemplateModel getTemplateModel() {
+        return model;
+    }
+
+    @Override
+    public int size() {
+        try {
+            return model.getCollectionSize();
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @Override
+    public boolean isEmpty() {
+        try {
+            return model.isEmptyCollection();
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        try {
+            return new TemplateModelIteratorAdapter<T>(model.iterator(), wrapper);
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java
new file mode 100644
index 0000000..b9ca888
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java
@@ -0,0 +1,185 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.AbstractMap;
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.NoSuchElementException;
+import java.util.Set;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ * Adapts a {@link TemplateHashModel} to a {@link Map}.
+ */
+class TemplateHashModelAdapter extends AbstractMap implements TemplateModelAdapter {
+    private final DefaultObjectWrapper wrapper;
+    private final TemplateHashModel model;
+    private Set entrySet;
+    
+    TemplateHashModelAdapter(TemplateHashModel model, DefaultObjectWrapper wrapper) {
+        this.model = model;
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    public TemplateModel getTemplateModel() {
+        return model;
+    }
+    
+    @Override
+    public boolean isEmpty() {
+        try {
+            return model.isEmptyHash();
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+    
+    @Override
+    public Object get(Object key) {
+        try {
+            return wrapper.unwrap(model.get(String.valueOf(key)));
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @Override
+    public boolean containsKey(Object key) {
+        // A quick check that doesn't require TemplateHashModelEx 
+        if (get(key) != null) {
+            return true;
+        }
+        return super.containsKey(key);
+    }
+    
+    @Override
+    public Set entrySet() {
+        if (entrySet != null) {
+            return entrySet;
+        }
+        return entrySet = new AbstractSet() {
+            @Override
+            public Iterator iterator() {
+                final TemplateModelIterator iterator;
+                try {
+                     iterator = getModelEx().keys().iterator();
+                } catch (TemplateException e) {
+                    throw new UndeclaredThrowableException(e);
+                }
+                return new Iterator() {
+                    @Override
+                    public boolean hasNext() {
+                        try {
+                            return iterator.hasNext();
+                        } catch (TemplateException e) {
+                            throw new UndeclaredThrowableException(e);
+                        }
+                    }
+                    
+                    @Override
+                    public Object next() {
+                        final Object key;
+                        try {
+                            if (!iterator.hasNext()) {
+                                throw new NoSuchElementException();
+                            }
+                            key = wrapper.unwrap(iterator.next());
+                        } catch (TemplateException e) {
+                            throw new UndeclaredThrowableException(e);
+                        }
+                        return new Map.Entry() {
+                            @Override
+                            public Object getKey() {
+                                return key;
+                            }
+                            
+                            @Override
+                            public Object getValue() {
+                                return get(key);
+                            }
+                            
+                            @Override
+                            public Object setValue(Object value) {
+                                throw new UnsupportedOperationException();
+                            }
+                            
+                            @Override
+                            public boolean equals(Object o) {
+                                if (!(o instanceof Map.Entry))
+                                    return false;
+                                Map.Entry e = (Map.Entry) o;
+                                Object k1 = getKey();
+                                Object k2 = e.getKey();
+                                if (k1 == k2 || (k1 != null && k1.equals(k2))) {
+                                    Object v1 = getValue();
+                                    Object v2 = e.getValue();
+                                    if (v1 == v2 || (v1 != null && v1.equals(v2))) 
+                                        return true;
+                                }
+                                return false;
+                            }
+                        
+                            @Override
+                            public int hashCode() {
+                                Object value = getValue();
+                                return (key == null ? 0 : key.hashCode()) ^
+                                       (value == null ? 0 : value.hashCode());
+                            }
+                        };
+                    }
+                    
+                    @Override
+                    public void remove() {
+                        throw new UnsupportedOperationException();
+                    }
+                };
+            }
+            
+            @Override
+            public int size() {
+                try {
+                    return getModelEx().getHashSize();
+                } catch (TemplateException e) {
+                    throw new UndeclaredThrowableException(e);
+                }
+            }
+        };
+    }
+    
+    private TemplateHashModelEx getModelEx() {
+        if (model instanceof TemplateHashModelEx) {
+            return ((TemplateHashModelEx) model);
+        }
+        throw new UnsupportedOperationException(
+                "Operation supported only on TemplateHashModelEx. " + 
+                model.getClass().getName() + " does not implement it though.");
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java
new file mode 100644
index 0000000..58e1835
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateIterableModelAdapter.java
@@ -0,0 +1,57 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Collection;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateIterableModel;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ * Adapts a {@link TemplateIterableModel} to  {@link Collection}.
+ */
+class TemplateIterableModelAdapter<T> implements TemplateModelAdapter, Iterable<T>  {
+    private final DefaultObjectWrapper wrapper;
+    private final TemplateIterableModel model;
+    
+    TemplateIterableModelAdapter(TemplateIterableModel model, DefaultObjectWrapper wrapper) {
+        this.model = model;
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    public TemplateModel getTemplateModel() {
+        return model;
+    }
+    
+    @Override
+    public Iterator<T> iterator() {
+        try {
+            return new TemplateModelIteratorAdapter<T>(model.iterator(), wrapper);
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java
new file mode 100644
index 0000000..263d6e0
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelIteratorAdapter.java
@@ -0,0 +1,65 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+class TemplateModelIteratorAdapter<T> implements Iterator<T> {
+    private final TemplateModelIterator iterator;
+    private final DefaultObjectWrapper wrapper;
+
+    public TemplateModelIteratorAdapter(TemplateModelIterator iterator,
+            DefaultObjectWrapper wrapper) {
+        this.iterator = iterator;
+        this.wrapper = wrapper;
+    }
+
+    @Override
+    public boolean hasNext() {
+        try {
+            return iterator.hasNext();
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public T next() {
+        try {
+            if (!iterator.hasNext()) {
+                throw new NoSuchElementException();
+            }
+            return (T) wrapper.unwrap(iterator.next());
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @Override
+    public void remove() {
+        throw new UnsupportedOperationException();
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java
index a93df33..db4dde1 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateModelListSequence.java
@@ -19,16 +19,17 @@
 
 package org.apache.freemarker.core.model.impl;
 
+import java.util.Iterator;
 import java.util.List;
 
-import org.apache.freemarker.core.model.TemplateFunctionModel;
+import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 
 /**
  * A sequence that wraps a {@link List} of {@link TemplateModel}-s. It does not copy the original
- * list. It's mostly useful when implementing {@link TemplateFunctionModel}-es that collect items from other
- * {@link TemplateModel}-s.
+ * list. The thread safety (and other behavior in face on concurrency) remains the same as of the wrapped list.
  */
 public class TemplateModelListSequence implements TemplateSequenceModel {
     
@@ -43,14 +44,36 @@ public class TemplateModelListSequence implements TemplateSequenceModel {
 
     @Override
     public TemplateModel get(int index) {
-        return list.get(index);
+        return index < list.size() && index >= 0 ? list.get(index) : null;
     }
 
     @Override
-    public int size() {
+    public int getCollectionSize() {
         return list.size();
     }
 
+    @Override
+    public boolean isEmptyCollection() throws TemplateException {
+        return list.isEmpty();
+    }
+
+    @Override
+    public TemplateModelIterator iterator() throws TemplateException {
+        return new TemplateModelIterator() {
+            private final Iterator<? extends TemplateModel> iterator = list.iterator();
+
+            @Override
+            public TemplateModel next() throws TemplateException {
+                return iterator.next();
+            }
+
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return iterator.hasNext();
+            }
+        };
+    }
+
     /**
      * Returns the original {@link List} of {@link TemplateModel}-s, so it's not a fully unwrapped value.
      */

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java
new file mode 100644
index 0000000..49da53a
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSequenceModelAdapter.java
@@ -0,0 +1,88 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.AbstractList;
+import java.util.Iterator;
+
+import org.apache.freemarker.core.TemplateException;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelAdapter;
+import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.util.UndeclaredThrowableException;
+
+/**
+ */
+class TemplateSequenceModelAdapter<T> extends AbstractList<T> implements TemplateModelAdapter {
+    private final DefaultObjectWrapper wrapper;
+    private final TemplateSequenceModel model;
+    
+    TemplateSequenceModelAdapter(TemplateSequenceModel model, DefaultObjectWrapper wrapper) {
+        this.model = model;
+        this.wrapper = wrapper;
+    }
+    
+    @Override
+    public TemplateModel getTemplateModel() {
+        return model;
+    }
+    
+    @Override
+    public int size() {
+        try {
+            return model.getCollectionSize();
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @Override
+    public boolean isEmpty() {
+        try {
+            return model.isEmptyCollection();
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    @Override
+    public T get(int index) {
+        try {
+            return (T) wrapper.unwrap(model.get(index));
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    @Override
+    public Iterator<T> iterator() {
+        try {
+            return new TemplateModelIteratorAdapter<T>(model.iterator(), wrapper);
+        } catch (TemplateException e) {
+            throw new UndeclaredThrowableException(e);
+        }
+    }
+
+    public TemplateSequenceModel getTemplateSequenceModel() {
+        return model;
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java
new file mode 100644
index 0000000..aa98bca
--- /dev/null
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateSetModelAdapter.java
@@ -0,0 +1,32 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import java.util.Set;
+
+import org.apache.freemarker.core.model.TemplateCollectionModel;
+
+// TODO [FM3] There's no TemplateSetModel, but we need to be able to do something like obj.needsASet([1, 2, 3]). But,
+// TemplateCollectionModel doesn't guarantee that the items are unique.
+class TemplateSetModelAdapter<T> extends TemplateCollectionModelAdapter<T> implements Set<T> {
+    TemplateSetModelAdapter(TemplateCollectionModel model, DefaultObjectWrapper wrapper) {
+        super(model, wrapper);
+    }
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java
index b3442d9..f305d3c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/CallableUtils.java
@@ -289,7 +289,7 @@ public final class CallableUtils {
         _ErrorDescriptionBuilder desc = new _ErrorDescriptionBuilder(
                 getMessageArgumentProblem(
                         callable, argIdx,
-                        new Object[]{ " should be ", new _DelayedAOrAn(expectedTypesDesc), ", but was ",
+                        new Object[]{ "should be ", new _DelayedAOrAn(expectedTypesDesc), ", but was ",
                                 new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(argValue)),
                                 "." },
                         calledAsFunction));
@@ -543,25 +543,33 @@ public final class CallableUtils {
     }
 
     /**
-     * Convenience method to call
+     * Convenience method to return {@code null} if the argument is missing, otherwise call
      * {@link #castArgumentValueToInt(TemplateModel, int, boolean, int, TemplateCallableModel, boolean)
-     * castArgumentValueToInt(args[argIndex], argIndex, true, null, callable, true)}.
+     * castArgumentValueToInt(args[argIndex], argIndex, false, 0, callable, false)}.
      */
-    public static int getOptionalIntArgument(
+    public static Integer getOptionalIntArgument(
             TemplateModel[] args, int argIndex, TemplateFunctionModel callable)
             throws TemplateException {
-        return castArgumentValueToInt(args[argIndex], argIndex, true, 0, callable, true);
+        TemplateModel argValue = args[argIndex];
+        if (argValue == null) {
+            return null;
+        }
+        return castArgumentValueToInt(argValue, argIndex, false, 0, callable, true);
     }
 
     /**
-     * Convenience method to call
+     * Convenience method to return {@code null} if the argument is missing, otherwise call
      * {@link #castArgumentValueToInt(TemplateModel, int, boolean, int, TemplateCallableModel, boolean)
-     * castArgumentValueToInt(args[argIndex], argIndex, true, null, callable, false)}.
+     * castArgumentValueToInt(args[argIndex], argIndex, false, 0, callable, false)}.
      */
-    public static int getOptionalIntArgument(
+    public static Integer getOptionalIntArgument(
             TemplateModel[] args, int argIndex, TemplateDirectiveModel callable)
             throws TemplateException {
-        return castArgumentValueToInt(args[argIndex], argIndex, true, 0, callable, false);
+        TemplateModel argValue = args[argIndex];
+        if (argValue == null) {
+            return null;
+        }
+        return castArgumentValueToInt(argValue, argIndex, false, 0, callable, false);
     }
 
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java
index 62bc25d..eb674cb 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/DeepUnwrap.java
@@ -27,14 +27,14 @@ import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.AdapterTemplateModel;
 import org.apache.freemarker.core.model.ObjectWrapper;
 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.TemplateHashModelEx;
+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.TemplateStringModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
 
 /**
@@ -61,7 +61,7 @@ public class DeepUnwrap {
      *   <li>If the object implements {@link TemplateBooleanModel}, then the result
      *       of {@link TemplateBooleanModel#getAsBoolean()} is returned.
      *   <li>If the object implements {@link TemplateSequenceModel} or
-     *       {@link TemplateCollectionModel}, then a <code>java.util.ArrayList</code> is
+     *       {@link TemplateIterableModel}, then a <code>java.util.ArrayList</code> is
      *       constructed from the subvariables, and each subvariable is unwrapped with
      *       the rules described here (recursive unwrapping).
      *   <li>If the object implements {@link TemplateHashModelEx}, then a
@@ -119,14 +119,16 @@ public class DeepUnwrap {
         }
         if (model instanceof TemplateSequenceModel) {
             TemplateSequenceModel seq = (TemplateSequenceModel) model;
-            ArrayList list = new ArrayList(seq.size());
-            for (int i = 0; i < seq.size(); ++i) {
-                list.add(unwrap(seq.get(i), nullModel, permissive));
+            int size = seq.getCollectionSize();
+            ArrayList list = new ArrayList(size);
+            TemplateModelIterator iter = seq.iterator();
+            for (int i = 0; i < size; ++i) {
+                list.add(unwrap(iter.next(), nullModel, permissive));
             }
             return list;
         }
-        if (model instanceof TemplateCollectionModel) {
-            TemplateCollectionModel coll = (TemplateCollectionModel) model;
+        if (model instanceof TemplateIterableModel) {
+            TemplateIterableModel coll = (TemplateIterableModel) model;
             ArrayList list = new ArrayList();
             TemplateModelIterator it = coll.iterator();            
             while (it.hasNext()) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java
index feb9586..830a41f 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/util/StringToIndexMap.java
@@ -247,9 +247,11 @@ public final class StringToIndexMap {
     }
 
     /**
-     * Checks if all entries are in the {@code start} - {@code start}+{@code size()} (exclusive) index range.
+     * Checks if all entries are in the {@code start} - {@code start}+{@code getCollectionSize()} (exclusive) index
+     * range.
      *
-     * @throws IllegalArgumentException If some entry is not in the specified index range.
+     * @throws IllegalArgumentException
+     *         If some entry is not in the specified index range.
      */
     public void checkIndexRange(int start) {
         if (buckets == null) {