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/10/19 23:23:30 UTC

incubator-freemarker git commit: Moved the keyValuePairIterator() method from TemplateHashModelEx2 to TemplateHashModelEx. (Now TemplateHashModelEx2 is unnecessary, but I haven't removed it yet.)

Repository: incubator-freemarker
Updated Branches:
  refs/heads/3 22ecffb4b -> 884d22afd


Moved the keyValuePairIterator() method from TemplateHashModelEx2 to TemplateHashModelEx. (Now TemplateHashModelEx2 is unnecessary, but I haven't removed it yet.)


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

Branch: refs/heads/3
Commit: 884d22afd8525a2df731c94e1fbc0bfb3000b63a
Parents: 22ecffb
Author: ddekany <dd...@apache.org>
Authored: Fri Oct 20 01:23:14 2017 +0200
Committer: ddekany <dd...@apache.org>
Committed: Fri Oct 20 01:23:14 2017 +0200

----------------------------------------------------------------------
 FM3-CHANGE-LOG.txt                              |   4 +
 .../impl/Java8DefaultObjectWrapperTest.java     |   8 +-
 .../freemarker/core/ConcatenatedHashTest.java   | 217 +++++++++++++++++++
 .../freemarker/core/ListBreakContinueTest.java  |   6 +
 .../model/impl/DefaultObjectWrapperTest.java    |   8 +-
 .../templatesuite/models/AllTemplateModels.java |   5 +
 .../models/HashAndStringModel.java              |   5 +
 .../core/templatesuite/models/Listables.java    |   7 +
 .../core/userpkg/TestTemplateCallableModel.java |   5 +-
 .../org/apache/freemarker/core/ASTDirList.java  |  10 +-
 .../freemarker/core/ASTExpAddOrConcat.java      | 169 +++++++++++----
 .../apache/freemarker/core/ASTExpDefault.java   |   5 +-
 .../freemarker/core/ASTExpHashLiteral.java      |   9 +-
 .../org/apache/freemarker/core/Environment.java |   7 +-
 .../apache/freemarker/core/NativeHashEx2.java   |   9 +-
 .../core/debug/RmiDebuggedEnvironmentImpl.java  |  29 +++
 .../freemarker/core/model/EmptyHashModel.java   |   2 +-
 .../core/model/EmptyKeyValuePairIterator.java   |   4 +-
 .../core/model/GeneralPurposeNothing.java       |   2 +-
 .../core/model/TemplateHashModelEx.java         |  48 ++++
 .../core/model/TemplateHashModelEx2.java        |  50 +----
 .../freemarker/core/model/impl/BeanModel.java   |  40 +++-
 .../core/model/impl/ClassIntrospector.java      |   3 +-
 .../core/model/impl/DefaultMapAdapter.java      |   2 +-
 .../model/impl/MapKeyValuePairIterator.java     |  10 +-
 .../freemarker/core/model/impl/SimpleHash.java  |   2 +-
 .../freemarker/core/model/impl/StaticModel.java |   5 +
 .../apache/freemarker/core/util/DeepUnwrap.java |   4 +-
 .../servlet/HttpRequestHashModel.java           |  28 +++
 .../servlet/HttpRequestParametersHashModel.java |  32 +++
 .../freemarker/servlet/jsp/JspTagModelBase.java |   5 +-
 .../freemarker/spring/model/UrlFunction.java    |   7 +-
 32 files changed, 606 insertions(+), 141 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/FM3-CHANGE-LOG.txt
----------------------------------------------------------------------
diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt
index 0480d51..8564541 100644
--- a/FM3-CHANGE-LOG.txt
+++ b/FM3-CHANGE-LOG.txt
@@ -434,6 +434,10 @@ Core / Models and Object wrapping
     - `TemplateHashModelEx.keys()` and `values()` returns `TemplateCollectoinModel`, just as in FM2, but in FM3
       `TemplateCollectoinModel` has `getCollectionSize()` and `isEmptyCollection` method. So this affects
       `TemplateHashModelEx` implementations as well.
+  - Map-like interfaces:
+    - [TODO: in progress] `TemplateHashModelEx2` was removed, as the `keyValuePairIterator()` method was moved to `TemplateHashModelEx`,
+       so now the two interfaces would be the same.
+- BeanModel.keys() and values() are now final methods. Override BeanModel.keySet() and/or get(String) instead.
       
 Core / Template loading and caching
 ...................................

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
index 2afe94c..fdeac75 100644
--- a/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
+++ b/freemarker-core-test-java8/src/test/java/org/apache/freemarker/core/model/impl/Java8DefaultObjectWrapperTest.java
@@ -23,20 +23,20 @@ import static org.junit.Assert.*;
 import org.apache.freemarker.core.Configuration;
 import org.apache.freemarker.core.NonTemplateCallPlace;
 import org.apache.freemarker.core.TemplateException;
-import org.apache.freemarker.core.model.TemplateStringModel;
-import org.apache.freemarker.core.util.CallableUtils;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
+import org.apache.freemarker.core.model.TemplateStringModel;
+import org.apache.freemarker.core.util.CallableUtils;
 import org.junit.Test;
 
 public class Java8DefaultObjectWrapperTest {
+    
+    private static final DefaultObjectWrapper ow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();    
 
     @Test
     public void testDefaultMethodRecognized() throws TemplateException {
-        DefaultObjectWrapper.Builder owb = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0);
-        DefaultObjectWrapper ow = owb.build();
         TemplateHashModel wrappedBean = (TemplateHashModel) ow.wrap(new Java8DefaultMethodsBean());
         
         {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConcatenatedHashTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConcatenatedHashTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConcatenatedHashTest.java
new file mode 100644
index 0000000..70c5c07
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ConcatenatedHashTest.java
@@ -0,0 +1,217 @@
+package org.apache.freemarker.core;
+
+import static org.hamcrest.CoreMatchers.*;
+import static org.junit.Assert.*;
+
+import java.io.IOException;
+import java.io.StringWriter;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Map.Entry;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.ObjectWrappingException;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePair;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePairIterator;
+import org.apache.freemarker.core.model.TemplateModel;
+import org.apache.freemarker.core.model.TemplateModelIterator;
+import org.junit.Test;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+
+public class ConcatenatedHashTest {
+
+    private static final ImmutableMap<String, Integer> ABCD_MAP = ImmutableMap.of("a", 1, "b", 2, "c", 3, "d", 4);
+    private static final ImmutableMap<String, Integer> CD_MAP = ImmutableMap.of("c", 3, "d", 4);
+    private static final ImmutableMap<String, Integer> AB_MAP = ImmutableMap.of("a", 1, "b", 2);
+    private static final ImmutableMap<String, Integer> ABC33_MAP = ImmutableMap.of("a", 1, "b", 2, "c", 33);
+    private static final ImmutableMap<String, Integer> C33DAB_MAP = ImmutableMap.of("c", 33, "d", 4, "a", 1, "b", 2);
+    
+    private static final Configuration cfg = new Configuration.Builder(Configuration.VERSION_3_0_0).build();
+    private static final ObjectWrapperAndUnwrapper ow = (ObjectWrapperAndUnwrapper) cfg.getObjectWrapper();
+
+    @Test
+    public void testHashPlusHash() throws Exception {
+        TemplateHashModel r = getConcatenatedModel(toHashModelNonEx(AB_MAP), toHashModelNonEx(CD_MAP));
+        assertThat(r, not(instanceOf(TemplateHashModelEx.class)));
+        assertHashModelContent(ABCD_MAP,
+                r);
+        assertHashModelContent(ABCD_MAP,
+                getConcatenatedModel(toHashModelNonEx(ABC33_MAP), toHashModelNonEx(CD_MAP)));
+        assertHashModelContent(C33DAB_MAP,
+                getConcatenatedModel(toHashModelNonEx(CD_MAP), toHashModelNonEx(ABC33_MAP)));
+        assertHashModelContent(AB_MAP,
+                getConcatenatedModel(
+                        toHashModelNonEx(AB_MAP), toHashModelNonEx(Collections.<String, Object>emptyMap())));
+        assertHashModelContent(AB_MAP,
+                getConcatenatedModel(
+                        toHashModelNonEx(Collections.<String, Object>emptyMap()), toHashModelNonEx(AB_MAP)));
+        assertHashModelContent(Collections.<String, Object>emptyMap(),
+                getConcatenatedModel(
+                        toHashModelNonEx(Collections.<String, Object>emptyMap()),
+                        toHashModelNonEx(Collections.<String, Object>emptyMap())));
+    }
+
+    @Test
+    public void testHashExPlusHashEx() throws Exception {
+        TemplateHashModel r = getConcatenatedModel(toHashModelEx(AB_MAP), toHashModelEx(CD_MAP));
+        assertThat(r, instanceOf(TemplateHashModelEx.class));
+        assertHashModelContent(ABCD_MAP,
+                r);
+        assertHashModelContent(ABCD_MAP,
+                getConcatenatedModel(toHashModelEx(ABC33_MAP), toHashModelEx(CD_MAP)));
+        assertHashModelContent(C33DAB_MAP,
+                getConcatenatedModel(toHashModelEx(CD_MAP), toHashModelEx(ABC33_MAP)));
+        assertHashModelContent(AB_MAP,
+                getConcatenatedModel(
+                        toHashModelEx(AB_MAP), toHashModelEx(Collections.<String, Object>emptyMap())));
+        assertHashModelContent(AB_MAP,
+                getConcatenatedModel(
+                        toHashModelEx(Collections.<String, Object>emptyMap()), toHashModelEx(AB_MAP)));
+        assertHashModelContent(Collections.<String, Object>emptyMap(),
+                getConcatenatedModel(
+                        toHashModelEx(Collections.<String, Object>emptyMap()),
+                        toHashModelEx(Collections.<String, Object>emptyMap())));
+    }
+
+    @Test
+    public void testNonStringKeyHashExPlusHashEx() throws Exception {
+        TemplateHashModelEx r = (TemplateHashModelEx) getConcatenatedModel(
+                toHashModelEx(ImmutableMap.of(1, "one", 2, "two", Locale.GERMAN, "de", Locale.FRANCE, "fr")),
+                toHashModelEx(ImmutableMap.of(2, "two v2", 3, "three", Locale.FRANCE, "fr v2")));
+        assertEquals(ImmutableList.of(1, 2, Locale.GERMAN, Locale.FRANCE, 3), unwrappedKeysToList(r));
+        assertEquals(ImmutableList.of("one", "two v2", "de", "fr v2", "three"), unwrappedValuesToList(r));
+    }
+    
+    private TemplateHashModel getConcatenatedModel(TemplateHashModel h1, TemplateHashModel h2)
+            throws IOException, TemplateException {
+        StringWriter sw = new StringWriter();
+        Environment env = new Template(null, "<#assign r = h1 + h2>", cfg)
+                .createProcessingEnvironment(ImmutableMap.of("h1", h1, "h2", h2), sw);
+        env.process();
+        return (TemplateHashModel) env.getVariable("r");
+    }
+
+    private TemplateHashModelEx toHashModelEx(Map<?, ?> map) throws ObjectWrappingException {
+        TemplateModel tm = cfg.getObjectWrapper().wrap(map);
+        assertThat(tm, instanceOf(TemplateHashModelEx.class));
+        return (TemplateHashModelEx) tm;
+    }
+
+    private TemplateHashModel toHashModelNonEx(Map<String, ?> map) throws ObjectWrappingException {
+        final TemplateHashModelEx tm = toHashModelEx(map);
+        return new TemplateHashModel() {
+
+            @Override
+            public boolean isEmptyHash() throws TemplateException {
+                return tm.isEmptyHash();
+            }
+
+            @Override
+            public TemplateModel get(String key) throws TemplateException {
+                return tm.get(key);
+            }
+        };
+    }
+
+    private void assertHashModelContent(Map<String, ?> expected, TemplateHashModel actual) throws TemplateException {
+        for (Entry<String, ?> ent : expected.entrySet()) {
+            TemplateModel value = actual.get(ent.getKey());
+            assertNotNull(value);
+            assertEquals(ent.getValue(), ow.unwrap(value));
+        }
+
+        assertEquals(expected.isEmpty(), actual.isEmptyHash());
+
+        if (actual instanceof TemplateHashModelEx) {
+            TemplateHashModelEx actualEx = (TemplateHashModelEx) actual;
+
+            assertEquals(expected.size(), actualEx.getHashSize());
+
+            // Keys:
+            {
+                ArrayList<String> expectedKeys = new ArrayList<>(expected.keySet());
+
+                ArrayList<?> actualKeys = unwrappedKeysToList(actualEx);
+                assertEquals(expectedKeys, actualKeys);
+
+                // Without hasNext:
+                ArrayList<String> actualKeys2 = new ArrayList<>();
+                TemplateModelIterator iter = actualEx.keys().iterator();
+                for (int i = 0; i < actualEx.getHashSize(); i++) {
+                    actualKeys2.add((String) ow.unwrap(iter.next()));
+                }
+                assertEquals(actualKeys, actualKeys2);
+                
+                assertEquals(expectedKeys.size(), actualEx.keys().getCollectionSize());
+                assertEquals(expectedKeys.isEmpty(), actualEx.keys().isEmptyCollection());
+            }
+
+            // Values:
+            {
+                ArrayList<?> expectedValues = new ArrayList<>(expected.values());
+
+                ArrayList<Object> actualValues = unwrappedValuesToList(actualEx);
+                assertEquals(expectedValues, actualValues);
+
+                // Without hasNext:
+                ArrayList<Object> actualValues2 = new ArrayList<>();
+                TemplateModelIterator iter = actualEx.values().iterator();
+                for (int i = 0; i < actualEx.getHashSize(); i++) {
+                    actualValues2.add(ow.unwrap(iter.next()));
+                }
+                assertEquals(actualValues, actualValues2);
+                
+                assertEquals(expectedValues.size(), actualEx.values().getCollectionSize());
+                assertEquals(expectedValues.isEmpty(), actualEx.values().isEmptyCollection());
+            }
+
+            // Key-value pairs:
+            {
+                ArrayList<Pair<String, ?>> expectedPairs = new ArrayList<>();
+                for (Map.Entry<String, ?> ent : expected.entrySet()) {
+                    expectedPairs.add(Pair.of(ent.getKey(), ent.getValue()));
+                }
+
+                ArrayList<Pair<String, ?>> actualPairs = new ArrayList<>();
+                for (KeyValuePairIterator iter = actualEx.keyValuePairIterator(); iter.hasNext();) {
+                    KeyValuePair kvp = iter.next();
+                    actualPairs.add(Pair.of((String) ow.unwrap(kvp.getKey()), ow.unwrap(kvp.getValue())));
+                }
+                assertEquals(expectedPairs, actualPairs);
+
+                // Without hasNext:
+                ArrayList<Pair<String, ?>> actualPairs2 = new ArrayList<>();
+                KeyValuePairIterator iter = actualEx.keyValuePairIterator();
+                for (int i = 0; i < actualEx.getHashSize(); i++) {
+                    KeyValuePair kvp = iter.next();
+                    actualPairs2.add(Pair.of((String) ow.unwrap(kvp.getKey()), ow.unwrap(kvp.getValue())));
+                }
+                assertEquals(actualPairs, actualPairs2);
+            }
+        }
+    }
+
+    private ArrayList<?> unwrappedKeysToList(TemplateHashModelEx actualEx) throws TemplateException {
+        ArrayList<Object> actualKeys = new ArrayList<>();
+        for (TemplateModelIterator iter = actualEx.keys().iterator(); iter.hasNext();) {
+            actualKeys.add(ow.unwrap(iter.next()));
+        }
+        return actualKeys;
+    }
+
+    private ArrayList<Object> unwrappedValuesToList(TemplateHashModelEx actualEx) throws TemplateException {
+        ArrayList<Object> actualValues = new ArrayList<>();
+        for (TemplateModelIterator iter = actualEx.values().iterator(); iter.hasNext();) {
+            actualValues.add(ow.unwrap(iter.next()));
+        }
+        return actualValues;
+    }
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test/src/test/java/org/apache/freemarker/core/ListBreakContinueTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ListBreakContinueTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ListBreakContinueTest.java
index 86f52f8..f7ef9b7 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/ListBreakContinueTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/ListBreakContinueTest.java
@@ -42,6 +42,7 @@ public class ListBreakContinueTest extends TemplateTest {
     }
     
     /** Hides the Ex2 features of another hash */
+    // TODO [FM3][CF] Remove
     static class NonEx2Hash implements TemplateHashModelEx {
         private final TemplateHashModelEx delegate;
 
@@ -73,6 +74,11 @@ public class ListBreakContinueTest extends TemplateTest {
         public TemplateCollectionModel values() throws TemplateException {
             return delegate.values();
         }
+
+        @Override
+        public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+            return delegate.keyValuePairIterator();
+        }
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
index ba71c6e..00b569f 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/DefaultObjectWrapperTest.java
@@ -51,16 +51,16 @@ import org.apache.freemarker.core.model.AdapterTemplateModel;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.ObjectWrappingException;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
-import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 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.TemplateModelWithAPISupport;
 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;
 import org.apache.freemarker.core.model.WrappingTemplateModel;
 import org.apache.freemarker.core.util.CallableUtils;
@@ -341,7 +341,7 @@ public class DefaultObjectWrapperTest {
             assertEquals("c", ((TemplateStringModel) seq.get(2)).getAsString());
             assertNull(seq.get(3));
 
-            assertCollectionTMEquals((TemplateIterableModel) seq, 1, null, "c");
+            assertCollectionTMEquals(seq, 1, null, "c");
 
             TemplateModelIterator it = ((TemplateIterableModel) seq).iterator();
             it.next();
@@ -900,5 +900,5 @@ public class DefaultObjectWrapperTest {
         }
 
     }
-
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/AllTemplateModels.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/AllTemplateModels.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/AllTemplateModels.java
index a5b387c..9fc5a8b 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/AllTemplateModels.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/AllTemplateModels.java
@@ -89,6 +89,11 @@ public class AllTemplateModels implements
     }
 
     @Override
+    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+        return KeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR;
+    }
+
+    @Override
     public boolean getAsBoolean() throws TemplateException {
         return true;
     }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/HashAndStringModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/HashAndStringModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/HashAndStringModel.java
index 9586b30..3c0fe45 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/HashAndStringModel.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/HashAndStringModel.java
@@ -60,4 +60,9 @@ public class HashAndStringModel implements TemplateHashModelEx, TemplateStringMo
         return TemplateCollectionModel.EMPTY_COLLECTION;
     }
 
+    @Override
+    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+        return KeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR;
+    }
+
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/Listables.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/Listables.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/Listables.java
index 6f83960..f5b9516 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/Listables.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/templatesuite/models/Listables.java
@@ -39,6 +39,7 @@ import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.WrappingTemplateModel;
 import org.apache.freemarker.core.model.impl.DefaultMapAdapter;
 import org.apache.freemarker.core.model.impl.DefaultObjectWrapper;
+import org.apache.freemarker.core.model.impl.MapKeyValuePairIterator;
 import org.apache.freemarker.core.model.impl.SimpleCollection;
 import org.apache.freemarker.core.model.impl.SimpleHash;
 
@@ -147,6 +148,7 @@ public class Listables {
                 new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build());
     }
     
+    // TODO [FM3][CF] Remove
     public static class NonEx2MapAdapter extends WrappingTemplateModel implements TemplateHashModelEx {
 
         private final Map<?, ?> map;
@@ -180,6 +182,11 @@ public class Listables {
         public TemplateCollectionModel values() {
             return new SimpleCollection(map.values(), getObjectWrapper());
         }
+
+        @Override
+        public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+            return new MapKeyValuePairIterator(map, getObjectWrapper());
+        }
         
     }
     

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
index 879c7d8..21c6ed5 100644
--- a/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/userpkg/TestTemplateCallableModel.java
@@ -24,6 +24,7 @@ import java.io.Writer;
 
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.TemplateCallableModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
@@ -76,10 +77,10 @@ public abstract class TestTemplateCallableModel implements TemplateCallableModel
             }
             sb.append(']');
         } else if (value instanceof TemplateHashModelEx2) {
-            TemplateHashModelEx2.KeyValuePairIterator it = ((TemplateHashModelEx2) value).keyValuePairIterator();
+            TemplateHashModelEx.KeyValuePairIterator it = ((TemplateHashModelEx2) value).keyValuePairIterator();
             sb.append('{');
             while (it.hasNext()) {
-                TemplateHashModelEx2.KeyValuePair kvp = it.next();
+                TemplateHashModelEx.KeyValuePair kvp = it.next();
 
                 printValue(kvp.getKey(), sb);
                 sb.append(": ");

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
index 0262ded..7e4e86c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirList.java
@@ -26,9 +26,9 @@ import java.util.Collections;
 
 import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePair;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
-import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePair;
-import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateIterableModel;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelIterator;
@@ -309,14 +309,14 @@ final class ASTDirList extends ASTDirective {
             if (listedValue instanceof TemplateHashModelEx) {
                 TemplateHashModelEx listedHash = (TemplateHashModelEx) listedValue; 
                 if (listedHash instanceof TemplateHashModelEx2) {
-                    KeyValuePairIterator kvpIter
+                    TemplateHashModelEx.KeyValuePairIterator kvpIter
                             = openedIterator == null ? ((TemplateHashModelEx2) listedHash).keyValuePairIterator()
-                                    : (KeyValuePairIterator) openedIterator;
+                                    : (TemplateHashModelEx.KeyValuePairIterator) openedIterator;
                     hashNotEmpty = kvpIter.hasNext();
                     if (hashNotEmpty) {
                         if (nestedContentParam1Name != null) {
                             listLoop: do {
-                                    KeyValuePair kvp = kvpIter.next();
+                                    TemplateHashModelEx.KeyValuePair kvp = kvpIter.next();
                                     nestedContentParam = kvp.getKey();
                                     nestedContentParam2 = kvp.getValue();
                                     hasNext = kvpIter.hasNext();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
index 230d1a9..320c427 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpAddOrConcat.java
@@ -19,13 +19,16 @@
 
 package org.apache.freemarker.core;
 
-import java.util.ArrayList;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Set;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.LinkedHashMap;
+import java.util.Map;
 
 import org.apache.freemarker.core.arithmetic.ArithmeticEngine;
+import org.apache.freemarker.core.model.AdapterTemplateModel;
+import org.apache.freemarker.core.model.TemplateBooleanModel;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateDateModel;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateMarkupOutputModel;
@@ -34,6 +37,7 @@ import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateSequenceModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
+import org.apache.freemarker.core.model.WrapperTemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleNumber;
 import org.apache.freemarker.core.model.impl.SimpleString;
 
@@ -123,9 +127,9 @@ final class ASTExpAddOrConcat extends ASTExpression {
         if (leftModel instanceof TemplateHashModelEx && rightModel instanceof TemplateHashModelEx) {
             TemplateHashModelEx leftModelEx = (TemplateHashModelEx) leftModel;
             TemplateHashModelEx rightModelEx = (TemplateHashModelEx) rightModel;
-            if (leftModelEx.getHashSize() == 0) {
+            if (leftModelEx.isEmptyHash()) {
                 return rightModelEx;
-            } else if (rightModelEx.getHashSize() == 0) {
+            } else if (rightModelEx.isEmptyHash()) {
                 return leftModelEx;
             } else {
                 return new ConcatenatedHashEx(leftModelEx, rightModelEx);
@@ -270,8 +274,8 @@ final class ASTExpAddOrConcat extends ASTExpression {
     }
 
     private static final class ConcatenatedHashEx extends ConcatenatedHash implements TemplateHashModelEx {
-        private TemplateCollectionModel keys;
-        private TemplateCollectionModel values;
+        /** Lazily calculated list of key-value pairs; there's only one item per duplicate key. */
+        private Collection<KeyValuePair> kvps;
 
         ConcatenatedHashEx(TemplateHashModelEx left, TemplateHashModelEx right) {
             super(left, right);
@@ -279,55 +283,140 @@ final class ASTExpAddOrConcat extends ASTExpression {
         
         @Override
         public int getHashSize() throws TemplateException {
-            initKeys();
-            return keys.getCollectionSize();
+            initKvps();
+            return kvps.size();
         }
 
         @Override
         public TemplateCollectionModel keys() throws TemplateException {
-            initKeys();
-            return keys;
+            initKvps();
+            return new TemplateCollectionModel() {
+                @Override
+                public TemplateModelIterator iterator() throws TemplateException {
+                    return new TemplateModelIterator() {
+                        private Iterator<KeyValuePair> iter = kvps.iterator();
+
+                        @Override
+                        public boolean hasNext() throws TemplateException {
+                            return iter.hasNext();
+                        }
+
+                        @Override
+                        public TemplateModel next() throws TemplateException {
+                            return iter.next().getKey();
+                        }
+                    };
+                }
+
+                @Override
+                public int getCollectionSize() throws TemplateException {
+                    return kvps.size();
+                }
+
+                @Override
+                public boolean isEmptyCollection() throws TemplateException {
+                    return kvps.isEmpty();
+                }
+            };
         }
 
         @Override
         public TemplateCollectionModel values() throws TemplateException {
-            initValues();
-            return values;
+            initKvps();
+            return new TemplateCollectionModel() {
+                @Override
+                public TemplateModelIterator iterator() throws TemplateException {
+                    return new TemplateModelIterator() {
+                        private Iterator<KeyValuePair> iter = kvps.iterator();
+
+                        @Override
+                        public boolean hasNext() throws TemplateException {
+                            return iter.hasNext();
+                        }
+
+                        @Override
+                        public TemplateModel next() throws TemplateException {
+                            return iter.next().getValue();
+                        }
+                    };
+                }
+                
+                @Override
+                public boolean isEmptyCollection() throws TemplateException {
+                    return kvps.isEmpty();
+                }
+                
+                @Override
+                public int getCollectionSize() throws TemplateException {
+                    return kvps.size();
+                }
+            };
+        }
+        
+        @Override
+        public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+            initKvps();
+            return new KeyValuePairIterator() {
+                private Iterator<KeyValuePair> iter = kvps.iterator();
+
+                @Override
+                public boolean hasNext() throws TemplateException {
+                    return iter.hasNext();
+                }
+
+                @Override
+                public KeyValuePair next() throws TemplateException {
+                    return iter.next();
+                }
+            };
         }
 
-        private void initKeys() throws TemplateException {
-            if (keys == null) {
-                HashSet keySet = new HashSet();
-                ArrayList<TemplateModel> keyList = new ArrayList<>();
-                addKeys(keySet, keyList, (TemplateHashModelEx) left);
-                addKeys(keySet, keyList, (TemplateHashModelEx) right);
-                keys = new NativeCollection(keyList);
+        /**
+         * We must precreate the whole key-value pair list, as we have to deal with duplicate keys. 
+         */
+        private void initKvps() throws TemplateException {
+            if (kvps != null) {
+                return;
             }
+            
+            Map<Object, KeyValuePair> kvpsMap = new LinkedHashMap<>();
+            putKVPs(kvpsMap, (TemplateHashModelEx) left);
+            putKVPs(kvpsMap, (TemplateHashModelEx) right);
+            this.kvps = kvpsMap.values();
         }
 
-        private static void addKeys(Set keySet, List<TemplateModel> keyList, TemplateHashModelEx hash)
-        throws TemplateException {
-            for (TemplateModelIterator it = hash.keys().iterator(); it.hasNext(); ) {
-                TemplateStringModel tsm = (TemplateStringModel) it.next();
-                if (keySet.add(tsm.getAsString())) {
-                    // The first occurrence of the key decides the index;
-                    // this is consistent with stuff like java.util.LinkedHashSet.
-                    keyList.add(tsm);
-                }
+        private static void putKVPs(Map<Object, KeyValuePair> kvps, TemplateHashModelEx hash) throws TemplateException {
+            for (KeyValuePairIterator iter = hash.keyValuePairIterator(); iter.hasNext(); ) {
+                KeyValuePair kvp = iter.next();
+                kvps.put(unwrapKey(kvp.getKey()), kvp);
             }
-        }        
+        }
 
-        private void initValues() throws TemplateException {
-            if (values == null) {
-                ArrayList<TemplateModel> valueList = new ArrayList<>(getHashSize());
-                // Note: getHashSize() invokes initKeys()
-            
-                for (TemplateModelIterator iter = keys.iterator(); iter.hasNext(); ) {
-                    valueList.add(get(((TemplateStringModel) iter.next()).getAsString()));
-                }
-                values = new NativeCollection(valueList);
+        private static Object unwrapKey(TemplateModel model) throws TemplateException {
+            if (model instanceof AdapterTemplateModel) {
+                return ((AdapterTemplateModel) model).getAdaptedObject(Object.class);
             }
+            if (model instanceof WrapperTemplateModel) {
+                return ((WrapperTemplateModel) model).getWrappedObject();
+            }
+            if (model instanceof TemplateStringModel) {
+                return ((TemplateStringModel) model).getAsString();
+            }
+            if (model instanceof TemplateNumberModel) {
+                return ((TemplateNumberModel) model).getAsNumber();
+            }
+            if (model instanceof TemplateDateModel) {
+                return ((TemplateDateModel) model).getAsDate();
+            }
+            if (model instanceof TemplateBooleanModel) {
+                return Boolean.valueOf(((TemplateBooleanModel) model).getAsBoolean());
+            }
+            // TODO [FM3] Handle List-s, etc.? But wait until FM3 TM-s settle; we might will have TM.getWrappedObject().
+            return new TemplateException(
+                    "Can't unwrapp hash key of this type, yet (TODO): ",
+                    new _DelayedTemplateLanguageTypeDescription(model));
         }
+        
     }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
index 6b6d409..1a54521 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDefault.java
@@ -21,6 +21,7 @@ package org.apache.freemarker.core;
 
 
 import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelIterator;
@@ -83,8 +84,8 @@ class ASTExpDefault extends ASTExpression {
         }
 
         @Override
-        public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
-            return KeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR;
+        public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+            return TemplateHashModelEx.KeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR;
         }
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
index 7198f6a..c062c18 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpHashLiteral.java
@@ -25,6 +25,7 @@ import java.util.LinkedHashMap;
 import java.util.ListIterator;
 
 import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelIterator;
@@ -158,8 +159,8 @@ final class ASTExpHashLiteral extends ASTExpression {
         }
 
         @Override
-        public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
-            return new KeyValuePairIterator() {
+        public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+            return new TemplateHashModelEx.KeyValuePairIterator() {
                 private final TemplateModelIterator keyIterator = keys().iterator();
                 private final TemplateModelIterator valueIterator = values().iterator();
 
@@ -169,8 +170,8 @@ final class ASTExpHashLiteral extends ASTExpression {
                 }
 
                 @Override
-                public KeyValuePair next() throws TemplateException {
-                    return new KeyValuePair() {
+                public TemplateHashModelEx.KeyValuePair next() throws TemplateException {
+                    return new TemplateHashModelEx.KeyValuePair() {
                         private final TemplateModel key = keyIterator.next();
                         private final TemplateModel value = valueIterator.next();
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/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 e1f9993..75bada7 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
@@ -2266,6 +2266,11 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
                 public TemplateCollectionModel keys() throws TemplateException {
                     return ((TemplateHashModelEx) rootDataModel).keys();
                 }
+                
+                @Override
+                public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+                    return ((TemplateHashModelEx) rootDataModel).keyValuePairIterator();
+                }
 
                 @Override
                 public int getHashSize() throws TemplateException {
@@ -2907,7 +2912,7 @@ public final class Environment extends MutableProcessingConfiguration<Environmen
         }
 
         @Override
-        public KeyValuePairIterator keyValuePairIterator() {
+        public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() {
             ensureInitializedRTE();
             return super.keyValuePairIterator();
         }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/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 9b39e82..0074171 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
@@ -26,6 +26,7 @@ import java.util.Map;
 
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.TemplateCollectionModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleString;
@@ -60,8 +61,8 @@ class NativeHashEx2 implements TemplateHashModelEx2, Serializable {
     }
 
     @Override
-    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
-        return new KeyValuePairIterator() {
+    public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+        return new TemplateHashModelEx.KeyValuePairIterator() {
             private final Iterator<Map.Entry<String, TemplateModel>> entrySetIterator = map.entrySet().iterator();
 
             @Override
@@ -70,8 +71,8 @@ class NativeHashEx2 implements TemplateHashModelEx2, Serializable {
             }
 
             @Override
-            public KeyValuePair next() throws TemplateException {
-                return new KeyValuePair() {
+            public TemplateHashModelEx.KeyValuePair next() throws TemplateException {
+                return new TemplateHashModelEx.KeyValuePair() {
                     private final Map.Entry<String, TemplateModel> entry = entrySetIterator.next();
 
                     @Override

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
index d1c2154..7fad936 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/debug/RmiDebuggedEnvironmentImpl.java
@@ -153,6 +153,35 @@ class RmiDebuggedEnvironmentImpl extends RmiDebugModelImpl implements DebuggedEn
             return getHashSize() == 0;
         }
         
+        @Override
+        public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+            return new KeyValuePairIterator() {
+                private final Iterator<String> keyIter = keySet().iterator();
+                
+                @Override
+                public KeyValuePair next() throws TemplateException {
+                    return new KeyValuePair() {
+                        private final String key = keyIter.next();
+                        
+                        @Override
+                        public TemplateModel getValue() throws TemplateException {
+                            return get(key);
+                        }
+                        
+                        @Override
+                        public TemplateModel getKey() throws TemplateException {
+                            return new SimpleString(key);
+                        }
+                    };
+                }
+                
+                @Override
+                public boolean hasNext() throws TemplateException {
+                    return keyIter.hasNext();
+                }
+            };
+        }
+
         abstract Collection keySet();
 
         static List composeList(Collection c1, Collection c2) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyHashModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyHashModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyHashModel.java
index 69ebe26..20aebe2 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyHashModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyHashModel.java
@@ -51,7 +51,7 @@ class EmptyHashModel implements TemplateHashModelEx2, Serializable {
     }
 
     @Override
-    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+    public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() throws TemplateException {
         return EmptyKeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR;
     }
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyKeyValuePairIterator.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyKeyValuePairIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyKeyValuePairIterator.java
index 83a0650..9552111 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyKeyValuePairIterator.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/EmptyKeyValuePairIterator.java
@@ -23,7 +23,7 @@ import java.util.NoSuchElementException;
 
 import org.apache.freemarker.core.TemplateException;
 
-class EmptyKeyValuePairIterator implements TemplateHashModelEx2.KeyValuePairIterator {
+class EmptyKeyValuePairIterator implements TemplateHashModelEx.KeyValuePairIterator {
 
     @Override
     public boolean hasNext() throws TemplateException {
@@ -31,7 +31,7 @@ class EmptyKeyValuePairIterator implements TemplateHashModelEx2.KeyValuePairIter
     }
 
     @Override
-    public TemplateHashModelEx2.KeyValuePair next() throws TemplateException {
+    public TemplateHashModelEx.KeyValuePair next() throws TemplateException {
         throw new NoSuchElementException("Can't retrieve element from empty key-value pair iterator.");
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
index 438b704..cf3490e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/GeneralPurposeNothing.java
@@ -108,7 +108,7 @@ implements TemplateBooleanModel, TemplateStringModel, TemplateSequenceModel, Tem
     }
 
     @Override
-    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+    public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() throws TemplateException {
         return EmptyKeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR;
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx.java
index 2b5c3a6..30ad102 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx.java
@@ -19,6 +19,8 @@
 
 package org.apache.freemarker.core.model;
 
+import java.util.Iterator;
+
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.impl.SimpleHash;
 
@@ -33,6 +35,45 @@ import org.apache.freemarker.core.model.impl.SimpleHash;
 public interface TemplateHashModelEx extends TemplateHashModel {
 
     /**
+     * A key-value pair in a hash; used for {@link TemplateHashModelEx.KeyValuePairIterator}.
+     */
+    interface KeyValuePair {
+        
+        /**
+         * @return Any type of {@link TemplateModel}, maybe {@code null} (if the hash entry key is {@code null}).
+         */
+        TemplateModel getKey() throws TemplateException;
+        
+        /**
+         * @return Any type of {@link TemplateModel}, maybe {@code null} (if the hash entry value is {@code null}).
+         */
+        TemplateModel getValue() throws TemplateException;
+    }
+
+    /**
+     * Iterates over the key-value pairs in a hash. This is very similar to an {@link Iterator}, but has a fixed item
+     * type, can throw {@link TemplateException}-s, and has no {@code remove()} method.
+     */
+    interface KeyValuePairIterator {
+    
+        TemplateHashModelEx.KeyValuePairIterator EMPTY_KEY_VALUE_PAIR_ITERATOR = new EmptyKeyValuePairIterator();
+    
+        /**
+         * Similar to {@link Iterator#hasNext()}.
+         */
+        boolean hasNext() throws TemplateException;
+        
+        /**
+         * Similar to {@link TemplateModelIterator#next()}, but returns a {@link KeyValuePair}-s instead of
+         * {@link TemplateModel}-s. As such, its behavior is undefined too, if it's called when there's no more
+         * items (so you must use {@link #hasNext()}, unless you know how many key-value pairs there are).
+         * 
+         * @return Not {@code null}
+         */
+        KeyValuePair next() throws TemplateException;
+    }
+
+    /**
      * @return the number of key/value mappings in the hash.
      */
     int getHashSize() throws TemplateException;
@@ -48,4 +89,11 @@ public interface TemplateHashModelEx extends TemplateHashModel {
      * {@link TemplateModel}-s.
      */
     TemplateCollectionModel values() throws TemplateException;
+    
+
+    /**
+     * @return The iterator that walks through the key-value pairs in the hash. Not {@code null}. 
+     */
+    TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() throws TemplateException;
+    
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
index cf8cee4..30a92d4 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/TemplateHashModelEx2.java
@@ -18,61 +18,13 @@
  */
 package org.apache.freemarker.core.model;
 
-import java.util.Iterator;
-import java.util.NoSuchElementException;
-
-import org.apache.freemarker.core.TemplateException;
-
 /**
  * Adds key-value pair listing capability to {@link TemplateHashModelEx}. While in many cases that can also be achieved
  * with {@link #keys()} and then {@link #get(String)}, that has some problems. One is that {@link #get(String)} only
  * accepts string keys, while {@link #keys()} can return non-string keys too. The other is that calling {@link #keys()}
  * and then {@link #get(String)} for each key can be slower than listing the key-value pairs in one go.
  */
+// TODO [FM3][CF] Remove
 public interface TemplateHashModelEx2 extends TemplateHashModelEx {
-
-    /**
-     * @return The iterator that walks through the key-value pairs in the hash. Not {@code null}. 
-     */
-    KeyValuePairIterator keyValuePairIterator() throws TemplateException;
-    
-    /**
-     * A key-value pair in a hash; used for {@link KeyValuePairIterator}.
-     */
-    interface KeyValuePair {
-        
-        /**
-         * @return Any type of {@link TemplateModel}, maybe {@code null} (if the hash entry key is {@code null}).
-         */
-        TemplateModel getKey() throws TemplateException;
-        
-        /**
-         * @return Any type of {@link TemplateModel}, maybe {@code null} (if the hash entry value is {@code null}).
-         */
-        TemplateModel getValue() throws TemplateException;
-    }
-    
-    /**
-     * Iterates over the key-value pairs in a hash. This is very similar to an {@link Iterator}, but has a fixed item
-     * type, can throw {@link TemplateException}-s, and has no {@code remove()} method.
-     */
-    interface KeyValuePairIterator {
-
-        TemplateHashModelEx2.KeyValuePairIterator EMPTY_KEY_VALUE_PAIR_ITERATOR = new EmptyKeyValuePairIterator();
-
-        /**
-         * Similar to {@link Iterator#hasNext()}.
-         */
-        boolean hasNext() throws TemplateException;
-        
-        /**
-         * Similar to {@link Iterator#next()}.
-         * 
-         * @return Not {@code null}
-         * 
-         * @throws NoSuchElementException
-         */
-        KeyValuePair next() throws TemplateException;
-    }
     
 }

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
index 9dbe5b6..b0c99cb 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/BeanModel.java
@@ -39,7 +39,6 @@ import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateFunctionModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
-import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.model.TemplateModelWithAPISupport;
 import org.apache.freemarker.core.model.TemplateStringModel;
 import org.apache.freemarker.core.model.WrapperTemplateModel;
@@ -274,22 +273,48 @@ public class BeanModel
     }
 
     @Override
-    public TemplateCollectionModel keys() {
+    public final TemplateCollectionModel keys() {
         return DefaultNonListCollectionAdapter.adapt(keySet(), wrapper);
     }
 
     @Override
-    public TemplateCollectionModel values() throws TemplateException {
-        List<Object> values = new ArrayList<>(getHashSize());
-        TemplateModelIterator it = keys().iterator();
-        while (it.hasNext()) {
-            String key = ((TemplateStringModel) it.next()).getAsString();
+    public final TemplateCollectionModel values() throws TemplateException {
+        Set<String> keySet = keySet();
+        List<Object> values = new ArrayList<>(keySet().size());
+        for (String key : keySet) {
             values.add(get(key));
         }
         return DefaultNonListCollectionAdapter.adapt(values, wrapper);
     }
     
     @Override
+    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+        final Iterator<String> keyIter = keySet().iterator();
+        return new KeyValuePairIterator() {
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return keyIter.hasNext();
+            }
+
+            @Override
+            public KeyValuePair next() throws TemplateException {
+                final String key = keyIter.next();
+                return new KeyValuePair() {
+                    @Override
+                    public TemplateModel getValue() throws TemplateException {
+                        return get(key);
+                    }
+                    
+                    @Override
+                    public TemplateModel getKey() throws TemplateException {
+                        return new SimpleString(key);
+                    }
+                };
+            };
+        };
+    }
+
+    @Override
     public String toString() {
         return object.toString();
     }
@@ -299,6 +324,7 @@ public class BeanModel
      * Strings which are available via the TemplateHashModel
      * interface. Subclasses that override <tt>invokeGenericGet</tt> to
      * provide additional hash keys should also override this method.
+     * Also, if this is overwritten, {@link #getHashSize()} should be too.
      */
     protected Set<String> keySet() {
         return wrapper.getClassIntrospector().keySet(object.getClass());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
index 94eca36..c6c9246 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/ClassIntrospector.java
@@ -959,6 +959,7 @@ class ClassIntrospector {
      * Returns the number of introspected methods/properties that should be available via the TemplateHashModel
      * interface.
      */
+    // TODO [FM3] Too slow. See also keySet().
     int keyCount(Class<?> clazz) {
         Map<Object, Object> map = get(clazz);
         int count = map.size();
@@ -972,7 +973,7 @@ class ClassIntrospector {
      * Returns the Set of names of introspected methods/properties that should be available via the TemplateHashModel
      * interface.
      */
-    // TODO [FM3] Can't we instead return an Iterable<String> that filters out the non-String keys?
+    // TODO [FM3] Far too slow. 
     @SuppressWarnings("rawtypes")
     Set<String> keySet(Class<?> clazz) {
         Set<Object> set = new HashSet<>(get(clazz).keySet());

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java
index f24d7c5..9f66026 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/DefaultMapAdapter.java
@@ -144,7 +144,7 @@ public class DefaultMapAdapter extends WrappingTemplateModel
     }
 
     @Override
-    public KeyValuePairIterator keyValuePairIterator() {
+    public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() {
         return new MapKeyValuePairIterator(map, getObjectWrapper());
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
index 80b6aa5..539267c 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/MapKeyValuePairIterator.java
@@ -25,16 +25,16 @@ import java.util.Map.Entry;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ObjectWrapper;
 import org.apache.freemarker.core.model.ObjectWrappingException;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
-import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePair;
-import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateModel;
 
 /**
  *  Implementation of {@link KeyValuePairIterator} for a {@link TemplateHashModelEx2} that wraps or otherwise uses a
  *  {@link Map} internally.
  */
-public class MapKeyValuePairIterator implements KeyValuePairIterator {
+public class MapKeyValuePairIterator implements TemplateHashModelEx.KeyValuePairIterator {
 
     private final Iterator<Entry<?, ?>> entrySetIterator;
     
@@ -52,9 +52,9 @@ public class MapKeyValuePairIterator implements KeyValuePairIterator {
     }
 
     @Override
-    public KeyValuePair next() {
+    public TemplateHashModelEx.KeyValuePair next() {
         final Entry<?, ?> entry = entrySetIterator.next();
-        return new KeyValuePair() {
+        return new TemplateHashModelEx.KeyValuePair() {
 
             @Override
             public TemplateModel getKey() throws TemplateException {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/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 38f2a9b..4ed6a8d 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
@@ -287,7 +287,7 @@ public class SimpleHash extends WrappingTemplateModel implements TemplateHashMod
     }
 
     @Override
-    public KeyValuePairIterator keyValuePairIterator() {
+    public TemplateHashModelEx.KeyValuePairIterator keyValuePairIterator() {
         return new MapKeyValuePairIterator(map, getObjectWrapper());
     }
 

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/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 684bc63..21c525c 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
@@ -102,6 +102,11 @@ final class StaticModel implements TemplateHashModelEx {
     public TemplateCollectionModel values() throws TemplateException {
         return (TemplateCollectionModel) wrapper.getOuterIdentity().wrap(map.values());
     }
+    
+    @Override
+    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+        return new MapKeyValuePairIterator(map, wrapper.getOuterIdentity());
+    }
 
     private void populate() throws TemplateException {
         if (!Modifier.isPublic(clazz.getModifiers())) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/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 eb674cb..500ce65 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
@@ -40,8 +40,8 @@ import org.apache.freemarker.core.model.WrapperTemplateModel;
 /**
  * Utility methods for unwrapping {@link TemplateModel}-s.
  */
+// TODO [FM3] Has to be changed or removed. For starters, for Collection-s and Map-s we should use adapters.
 public class DeepUnwrap {
-    private static final Class OBJECT_CLASS = Object.class;
     /**
      * Unwraps {@link TemplateModel}-s recursively.
      * The converting of the {@link TemplateModel} object happens with the following rules:
@@ -97,7 +97,7 @@ public class DeepUnwrap {
 
     private static Object unwrap(TemplateModel model, TemplateModel nullModel, boolean permissive) throws TemplateException {
         if (model instanceof AdapterTemplateModel) {
-            return ((AdapterTemplateModel) model).getAdaptedObject(OBJECT_CLASS);
+            return ((AdapterTemplateModel) model).getAdaptedObject(Object.class);
         }
         if (model instanceof WrapperTemplateModel) {
             return ((WrapperTemplateModel) model).getWrappedObject();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestHashModel.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestHashModel.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestHashModel.java
index 9a61294..690e3bc 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestHashModel.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestHashModel.java
@@ -32,6 +32,7 @@ import org.apache.freemarker.core.model.TemplateCollectionModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.impl.SimpleCollection;
+import org.apache.freemarker.core.model.impl.SimpleString;
 
 /**
  * TemplateHashModel wrapper for a HttpServletRequest attributes.
@@ -93,6 +94,33 @@ public final class HttpRequestHashModel implements TemplateHashModelEx {
         }
         return new SimpleCollection(values, wrapper);
     }
+    
+    @Override
+    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+        final Enumeration<String> namesEnum = request.getAttributeNames();
+        return new KeyValuePairIterator() {
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return namesEnum.hasMoreElements();
+            }
+
+            @Override
+            public KeyValuePair next() throws TemplateException {
+                final String name = namesEnum.nextElement();
+                return new KeyValuePair() {
+                    @Override
+                    public TemplateModel getValue() throws TemplateException {
+                        return wrapper.wrap(request.getAttribute(name));
+                    }
+                    
+                    @Override
+                    public TemplateModel getKey() throws TemplateException {
+                        return new SimpleString(name);
+                    }
+                };
+            }
+        };
+    }
 
     public HttpServletRequest getRequest() {
         return request;

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestParametersHashModel.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestParametersHashModel.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestParametersHashModel.java
index 573c4ad..104331a 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestParametersHashModel.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/HttpRequestParametersHashModel.java
@@ -105,6 +105,38 @@ public class HttpRequestParametersHashModel implements TemplateHashModelEx {
             }
         };
     }
+    
+    
+
+    @Override
+    public KeyValuePairIterator keyValuePairIterator() throws TemplateException {
+        return new KeyValuePairIterator() {
+            private final List<String> keys = getKeys();
+            private int nextIndex = 0;  
+            
+            @Override
+            public KeyValuePair next() throws TemplateException {
+                return new KeyValuePair() {
+                    private final String key = keys.get(nextIndex++);
+                    
+                    @Override
+                    public TemplateModel getValue() throws TemplateException {
+                        return objectWrapper.wrap(request.getParameter(key));
+                    }
+                    
+                    @Override
+                    public TemplateModel getKey() throws TemplateException {
+                        return new SimpleString(key);
+                    }
+                };
+            }
+            
+            @Override
+            public boolean hasNext() throws TemplateException {
+                return nextIndex < keys.size();
+            }
+        };
+    }
 
     private synchronized List<String> getKeys() {
         if (keys == null) {

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
----------------------------------------------------------------------
diff --git a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
index 75f1fe2..52e7f9d 100644
--- a/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
+++ b/freemarker-servlet/src/main/java/org/apache/freemarker/servlet/jsp/JspTagModelBase.java
@@ -39,6 +39,7 @@ import org.apache.freemarker.core._DelayedJQuote;
 import org.apache.freemarker.core._DelayedShortClassName;
 import org.apache.freemarker.core._ErrorDescriptionBuilder;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
 import org.apache.freemarker.core.model.TemplateModelWithOriginName;
 import org.apache.freemarker.core.model.TemplateStringModel;
@@ -74,8 +75,8 @@ abstract class JspTagModelBase implements TemplateModelWithOriginName {
         IllegalAccessException {
         if (args != null && !args.isEmptyHash()) {
             final Object[] argArray = new Object[1];
-            for (TemplateHashModelEx2.KeyValuePairIterator iter = args.keyValuePairIterator(); iter.hasNext(); ) {
-                final TemplateHashModelEx2.KeyValuePair entry = iter.next();
+            for (TemplateHashModelEx.KeyValuePairIterator iter = args.keyValuePairIterator(); iter.hasNext(); ) {
+                final TemplateHashModelEx.KeyValuePair entry = iter.next();
                 final Object arg = wrapper.unwrap(entry.getValue());
                 argArray[0] = arg;
                 final String paramName = ((TemplateStringModel) entry.getKey()).getAsString();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/884d22af/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
----------------------------------------------------------------------
diff --git a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
index 71220b1..3381f9c 100644
--- a/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
+++ b/freemarker-spring/src/main/java/org/apache/freemarker/spring/model/UrlFunction.java
@@ -37,8 +37,9 @@ import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.ArgumentArrayLayout;
 import org.apache.freemarker.core.model.ObjectWrapperAndUnwrapper;
 import org.apache.freemarker.core.model.TemplateBooleanModel;
+import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateHashModelEx2;
-import org.apache.freemarker.core.model.TemplateHashModelEx2.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateNumberModel;
 import org.apache.freemarker.core.model.TemplateStringModel;
@@ -114,8 +115,8 @@ class UrlFunction extends AbstractSpringTemplateFunctionModel {
         if (!paramsHashModel.isEmptyHash()) {
             params = new ArrayList<>();
 
-            for (KeyValuePairIterator pairIt = paramsHashModel.keyValuePairIterator(); pairIt.hasNext();) {
-                TemplateHashModelEx2.KeyValuePair pair = pairIt.next();
+            for (TemplateHashModelEx.KeyValuePairIterator pairIt = paramsHashModel.keyValuePairIterator(); pairIt.hasNext();) {
+                TemplateHashModelEx.KeyValuePair pair = pairIt.next();
                 TemplateModel paramNameModel = pair.getKey();
                 TemplateModel paramValueModel = pair.getValue();