Repository: incubator-freemarker
Updated Branches:
  refs/heads/2.3-gae c533df561 -> 59f2e7b8c


Added TemplateModelUtils.getKeyValuePairIterator(TemplateHashModelEx) static 
utility class, which can be used to get a 
TemplateHashModelEx2.KeyValuePairIterator even for a non-TemplateHashModelEx2 
object. This simplifies Java code that iterates through key-value pairs.


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

Branch: refs/heads/2.3-gae
Commit: 59f2e7b8c082b6405850fc2163b78fac3cbdd4dc
Parents: c533df5
Author: ddekany <ddek...@apache.org>
Authored: Wed Feb 28 11:31:37 2018 +0100
Committer: ddekany <ddek...@apache.org>
Committed: Wed Feb 28 11:31:37 2018 +0100

----------------------------------------------------------------------
 .../java/freemarker/core/IteratorBlock.java     |  13 +-
 src/main/java/freemarker/core/_MessageUtil.java |   8 +-
 .../template/utility/TemplateModelUtils.java    |  90 +++++++++++
 src/manual/en_US/book.xml                       |  12 +-
 .../template/utility/TemplateModelUtilTest.java | 155 +++++++++++++++++++
 5 files changed, 262 insertions(+), 16 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59f2e7b8/src/main/java/freemarker/core/IteratorBlock.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/IteratorBlock.java 
b/src/main/java/freemarker/core/IteratorBlock.java
index 83c0b22..bbb6773 100644
--- a/src/main/java/freemarker/core/IteratorBlock.java
+++ b/src/main/java/freemarker/core/IteratorBlock.java
@@ -392,17 +392,8 @@ final class IteratorBlock extends TemplateElement {
                             listLoop: do {
                                 loopVar = keysIter.next();
                                 if (!(loopVar instanceof TemplateScalarModel)) 
{
-                                    throw new NonStringException(env,
-                                            new _ErrorDescriptionBuilder(
-                                                    "When listing key-value 
pairs of traditional hash "
-                                                    + "implementations, all 
keys must be strings, but one of them "
-                                                    + "was ",
-                                                    new _DelayedAOrAn(new 
_DelayedFTLTypeDescription(loopVar)), "."
-                                                    ).tip("The listed value's 
TemplateModel class was ",
-                                                            new 
_DelayedShortClassName(listedValue.getClass()),
-                                                            ", which doesn't 
implement ",
-                                                            new 
_DelayedShortClassName(TemplateHashModelEx2.class),
-                                                            ", which leads to 
this restriction."));
+                                    throw 
_MessageUtil.newKeyValuePairListingNonStringKeyExceptionMessage(
+                                                loopVar, (TemplateHashModelEx) 
listedValue);
                                 }
                                 loopVar2 = 
listedHash.get(((TemplateScalarModel) loopVar).getAsString());
                                 hasNext = keysIter.hasNext();

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59f2e7b8/src/main/java/freemarker/core/_MessageUtil.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/core/_MessageUtil.java 
b/src/main/java/freemarker/core/_MessageUtil.java
index db097db..42cfac7 100644
--- a/src/main/java/freemarker/core/_MessageUtil.java
+++ b/src/main/java/freemarker/core/_MessageUtil.java
@@ -322,10 +322,10 @@ public class _MessageUtil {
                 ? new _TemplateModelException(e, (Environment) null, desc)
                 : new _MiscTemplateException(e, (Environment) null, desc);
     }
-    
-    public static _ErrorDescriptionBuilder 
traditionalHashExKeyMustBeStringExceptionMessage(
+
+    public static TemplateModelException 
newKeyValuePairListingNonStringKeyExceptionMessage(
             TemplateModel key, TemplateHashModelEx listedHashEx) {
-        return new _ErrorDescriptionBuilder(
+        return new _TemplateModelException(new _ErrorDescriptionBuilder(
                 "When listing key-value pairs of traditional hash "
                 + "implementations, all keys must be strings, but one of them "
                 + "was ",
@@ -334,7 +334,7 @@ public class _MessageUtil {
                         new _DelayedShortClassName(listedHashEx.getClass()),
                         ", which doesn't implement ",
                         new _DelayedShortClassName(TemplateHashModelEx2.class),
-                        ", which leads to this restriction.");
+                        ", which leads to this restriction."));
     }
     
     /**

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59f2e7b8/src/main/java/freemarker/template/utility/TemplateModelUtils.java
----------------------------------------------------------------------
diff --git a/src/main/java/freemarker/template/utility/TemplateModelUtils.java 
b/src/main/java/freemarker/template/utility/TemplateModelUtils.java
new file mode 100644
index 0000000..d33b6da
--- /dev/null
+++ b/src/main/java/freemarker/template/utility/TemplateModelUtils.java
@@ -0,0 +1,90 @@
+/*
+ * 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 freemarker.template.utility;
+
+import freemarker.core._MessageUtil;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateHashModelEx2;
+import freemarker.template.TemplateHashModelEx2.KeyValuePair;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateModelIterator;
+import freemarker.template.TemplateScalarModel;
+
+/**
+ * Static utility method related to {@link TemplateModel}-s that didn't fit 
elsewhere.
+ * 
+ * @since 2.3.28
+ */
+public final class TemplateModelUtils {
+
+    // Private to prevent instantiation
+    private TemplateModelUtils() {
+        // no op.
+    }
+
+    /**
+     * {@link TemplateHashModelExKeyValuePairIterator} that even works for a 
non-{@link TemplateHashModelEx2}
+     * {@link TemplateHashModelEx}. This is used to simplify code that needs 
to iterate through the key-value pairs of
+     * {@link TemplateHashModelEx}-s, as with this you don't have to handle 
non-{@link TemplateHashModelEx2}-s
+     * separately. For non-{@link TemplateHashModelEx2} values the iteration 
will throw {@link TemplateModelException}
+     * if it reaches a key that's not a string ({@link TemplateScalarModel}).
+     */
+    public static final TemplateHashModelEx2.KeyValuePairIterator 
getKeyValuePairIterator(TemplateHashModelEx hash)
+            throws TemplateModelException {
+        return hash instanceof TemplateHashModelEx2 ? ((TemplateHashModelEx2) 
hash).keyValuePairIterator()
+                : new TemplateHashModelExKeyValuePairIterator(hash);
+    }
+
+    private static class TemplateHashModelExKeyValuePairIterator implements 
TemplateHashModelEx2.KeyValuePairIterator {
+
+        private final TemplateHashModelEx hash;
+        private final TemplateModelIterator keyIter;
+
+        private TemplateHashModelExKeyValuePairIterator(TemplateHashModelEx 
hash) throws TemplateModelException {
+            this.hash = hash;
+            keyIter = hash.keys().iterator();
+        }
+
+        public boolean hasNext() throws TemplateModelException {
+            return keyIter.hasNext();
+        }
+
+        public KeyValuePair next() throws TemplateModelException {
+            final TemplateModel key = keyIter.next();
+            if (!(key instanceof TemplateScalarModel)) {
+                throw 
_MessageUtil.newKeyValuePairListingNonStringKeyExceptionMessage(key, hash);
+            }
+
+            return new KeyValuePair() {
+
+                public TemplateModel getKey() throws TemplateModelException {
+                    return key;
+                }
+
+                public TemplateModel getValue() throws TemplateModelException {
+                    return hash.get(((TemplateScalarModel) key).getAsString());
+                }
+
+            };
+        }
+
+    }
+
+}
\ No newline at end of file

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59f2e7b8/src/manual/en_US/book.xml
----------------------------------------------------------------------
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index b208ecd..56851f3 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -30,7 +30,7 @@
 
     <titleabbrev>Manual</titleabbrev>
 
-    <productname>Freemarker 2.3.27</productname>
+    <productname>Freemarker 2.3.28</productname>
   </info>
 
   <preface role="index.html" xml:id="preface">
@@ -27157,6 +27157,16 @@ TemplateModel x = env.getVariable("x");  // get 
variable x</programlisting>
                 </listitem>
               </itemizedlist>
             </listitem>
+
+            <listitem>
+              <para>Added
+              
<literal>TemplateModelUtils.getKeyValuePairIterator(TemplateHashModelEx)</literal>
+              static utility class, which can be used to get a
+              <literal>TemplateHashModelEx2.KeyValuePairIterator</literal>
+              even for a non-<literal>TemplateHashModelEx2</literal> object.
+              This simplifies Java code that iterates through key-value
+              pairs.</para>
+            </listitem>
           </itemizedlist>
         </section>
       </section>

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/59f2e7b8/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java
----------------------------------------------------------------------
diff --git 
a/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java 
b/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java
new file mode 100644
index 0000000..6179e17
--- /dev/null
+++ b/src/test/java/freemarker/template/utility/TemplateModelUtilTest.java
@@ -0,0 +1,155 @@
+/*
+ * 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 freemarker.template.utility;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.junit.Test;
+
+import freemarker.template.Configuration;
+import freemarker.template.DefaultMapAdapter;
+import freemarker.template.DefaultNonListCollectionAdapter;
+import freemarker.template.DefaultObjectWrapperBuilder;
+import freemarker.template.TemplateCollectionModel;
+import freemarker.template.TemplateHashModelEx;
+import freemarker.template.TemplateHashModelEx2.KeyValuePair;
+import freemarker.template.TemplateHashModelEx2.KeyValuePairIterator;
+import freemarker.template.TemplateModel;
+import freemarker.template.TemplateModelException;
+import freemarker.template.TemplateNumberModel;
+import freemarker.template.TemplateScalarModel;
+import freemarker.test.TemplateTest;
+
+public class TemplateModelUtilTest extends TemplateTest {
+
+    @Test
+    public void testGetKeyValuePairIterator() throws Exception {
+        Map<Object, Object> map = new LinkedHashMap<Object, Object>();
+        TemplateHashModelEx thme = new TemplateHashModelExOnly(map);
+        
+        assertetKeyValuePairIteratorResult("", thme);
+        
+        map.put("k1", 11);
+        assertetKeyValuePairIteratorResult("str(k1): num(11)", thme);
+        
+        map.put("k2", "v2");
+        assertetKeyValuePairIteratorResult("str(k1): num(11), str(k2): 
str(v2)", thme);
+
+        map.put("k2", null);
+        assertetKeyValuePairIteratorResult("str(k1): num(11), str(k2): null", 
thme);
+        
+        map.put(3, 33);
+        try {
+            assertetKeyValuePairIteratorResult("fails anyway...", thme);
+            fail();
+        } catch (TemplateModelException e) {
+            assertThat(e.getMessage(),
+                    allOf(containsString("keys must be"), 
containsString("string"), containsString("number")));
+        }
+        map.remove(3);
+        
+        map.put(null, 44);
+        try {
+            assertetKeyValuePairIteratorResult("fails anyway...", thme);
+            fail();
+        } catch (TemplateModelException e) {
+            assertThat(e.getMessage(),
+                    allOf(containsString("keys must be"), 
containsString("string"), containsString("Null")));
+        }
+    }
+
+    @Test
+    public void testGetKeyValuePairIteratorWithEx2() throws Exception {
+        Map<Object, Object> map = new LinkedHashMap<Object, Object>();
+        TemplateHashModelEx thme = DefaultMapAdapter.adapt(
+                map, (ObjectWrapperWithAPISupport) 
getConfiguration().getObjectWrapper());
+        
+        assertetKeyValuePairIteratorResult("", thme);
+        
+        map.put("k1", 11);
+        map.put("k2", "v2");
+        map.put("k2", null);
+        map.put(3, 33);
+        map.put(null, 44);
+        assertetKeyValuePairIteratorResult("str(k1): num(11), str(k2): null, 
num(3): num(33), null: num(44)", thme);
+    }
+    
+    private void assertetKeyValuePairIteratorResult(String expected, 
TemplateHashModelEx thme)
+            throws TemplateModelException {
+         StringBuilder sb = new StringBuilder();
+         KeyValuePairIterator kvpi = 
TemplateModelUtils.getKeyValuePairIterator(thme);
+         while (kvpi.hasNext()) {
+             KeyValuePair kvp = kvpi.next();
+             if (sb.length() != 0) {
+                 sb.append(", ");
+             }
+             sb.append(toAssertionString(kvp.getKey())).append(": 
").append(toAssertionString(kvp.getValue()));
+         }
+    }
+    
+    private String toAssertionString(TemplateModel model) throws 
TemplateModelException {
+        if (model instanceof TemplateNumberModel) {
+            return "num(" + ((TemplateNumberModel) model).getAsNumber() + ")";
+        } else if (model instanceof TemplateScalarModel) {
+            return "str(" + ((TemplateScalarModel) model).getAsString() + ")";
+        } else if (model == null) {
+            return "null";
+        }
+        
+        throw new IllegalArgumentException("Type unsupported by test: " + 
model.getClass().getName());
+    }
+
+    private static class TemplateHashModelExOnly implements 
TemplateHashModelEx {
+        
+        private final Map<?, ?> map;
+        private final ObjectWrapperWithAPISupport objectWrapper;
+        
+        public TemplateHashModelExOnly(Map<?, ?> map) {
+            this.map = map;
+            objectWrapper = new 
DefaultObjectWrapperBuilder(Configuration.VERSION_2_3_27).build();
+        }
+
+        public TemplateModel get(String key) throws TemplateModelException {
+            return objectWrapper.wrap(map.get(key));
+        }
+
+        public boolean isEmpty() throws TemplateModelException {
+            return map.isEmpty();
+        }
+
+        public int size() throws TemplateModelException {
+            return 2;
+        }
+
+        public TemplateCollectionModel keys() throws TemplateModelException {
+            return DefaultNonListCollectionAdapter.adapt(map.keySet(), 
objectWrapper);
+        }
+
+        public TemplateCollectionModel values() throws TemplateModelException {
+            return DefaultNonListCollectionAdapter.adapt(map.values(), 
objectWrapper);
+        } 
+        
+    }
+    
+}

Reply via email to