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 2018/03/01 17:08:39 UTC

[3/4] incubator-freemarker git commit: TemplateHashModelAdapter improvements (esp. that it utilizes KeyValuePairIterator)

TemplateHashModelAdapter improvements (esp. that it utilizes KeyValuePairIterator)


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

Branch: refs/heads/3
Commit: 9d96369ebde23d6e0a90862e3dfd0ff98c8242b1
Parents: 654fdad
Author: ddekany <dd...@apache.org>
Authored: Thu Mar 1 17:10:04 2018 +0100
Committer: ddekany <dd...@apache.org>
Committed: Thu Mar 1 17:10:04 2018 +0100

----------------------------------------------------------------------
 .../impl/TemplateHashModelAdapterTest.java      | 95 ++++++++++++++++++++
 .../model/impl/TemplateHashModelAdapter.java    | 86 ++++++++++++------
 2 files changed, 155 insertions(+), 26 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9d96369e/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapterTest.java
----------------------------------------------------------------------
diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapterTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapterTest.java
new file mode 100644
index 0000000..6b6e51a
--- /dev/null
+++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapterTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.freemarker.core.model.impl;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.Map;
+
+import org.apache.commons.lang3.tuple.Pair;
+import org.apache.freemarker.core.Configuration;
+import org.apache.freemarker.core.model.ObjectWrappingException;
+import org.apache.freemarker.core.model.TemplateHashModel;
+import org.junit.Test;
+
+public class TemplateHashModelAdapterTest {
+
+    @Test
+    public void testNonEmpty() throws ObjectWrappingException {
+        Map<Object, Object> map = new LinkedHashMap<Object, Object>();
+        map.put("k1", "v1");
+        map.put("k2", 2);
+        map.put("k3", null);
+        map.put(4, "v4");
+        map.put(null, "v5");
+        map.put("k6", true);
+
+        DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        TemplateHashModel model = (TemplateHashModel) dow.wrap(map);
+
+        TemplateHashModelAdapter<Object, Object> adapted = new TemplateHashModelAdapter<Object, Object>(model, dow);
+
+        assertEquals("v1", adapted.get("k1"));
+        assertEquals(2, adapted.get("k2"));
+        assertNull(adapted.get("k3"));
+        assertNull(adapted.get(4)); // Because it's not a string key
+        assertNull(adapted.get(null)); // Because it's not a string key
+        assertEquals(true, adapted.get("k6"));
+
+        assertArrayEquals(new Object[] { "k1", "k2", "k3", 4, null, "k6" }, adapted.keySet().toArray());
+        assertArrayEquals(new Object[] { "v1", 2, null, "v4", "v5", true }, adapted.values().toArray());
+        assertArrayEquals(
+                new Object[] {
+                        Pair.of("k1", "v1"),
+                        Pair.of("k2", 2),
+                        Pair.of("k3", null),
+                        Pair.of(4, "v4"),
+                        Pair.of(null, "v5"),
+                        Pair.of("k6", true)
+                },
+                adapted.entrySet().toArray());
+        
+        assertEquals(map.size(), adapted.size());
+        assertEquals(map.isEmpty(), adapted.isEmpty());
+    }
+
+    @Test
+    public void testEmpty() throws ObjectWrappingException {
+        Map<Object, Object> map = Collections.emptyMap();
+
+        DefaultObjectWrapper dow = new DefaultObjectWrapper.Builder(Configuration.VERSION_3_0_0).build();
+        TemplateHashModel model = (TemplateHashModel) dow.wrap(map);
+
+        TemplateHashModelAdapter<Object, Object> adapted = new TemplateHashModelAdapter<Object, Object>(model, dow);
+
+        assertNull(adapted.get("k1"));
+
+        assertThat(adapted.keySet(), empty());
+        assertThat(adapted.values(), empty());
+        assertThat(adapted.entrySet(), empty());
+        
+        assertEquals(map.size(), adapted.size());
+        assertEquals(map.isEmpty(), adapted.isEmpty());
+    }
+    
+}

http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/9d96369e/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java
----------------------------------------------------------------------
diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java
index 5f8a528..7197b6e 100644
--- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java
+++ b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/TemplateHashModelAdapter.java
@@ -29,18 +29,19 @@ import java.util.Set;
 import org.apache.freemarker.core.TemplateException;
 import org.apache.freemarker.core.model.TemplateHashModel;
 import org.apache.freemarker.core.model.TemplateHashModelEx;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePair;
+import org.apache.freemarker.core.model.TemplateHashModelEx.KeyValuePairIterator;
 import org.apache.freemarker.core.model.TemplateModel;
 import org.apache.freemarker.core.model.TemplateModelAdapter;
-import org.apache.freemarker.core.model.TemplateModelIterator;
 import org.apache.freemarker.core.util.UndeclaredThrowableException;
 
 /**
  * Adapts a {@link TemplateHashModel} to a {@link Map}.
  */
-class TemplateHashModelAdapter extends AbstractMap implements TemplateModelAdapter {
+class TemplateHashModelAdapter<K, V> extends AbstractMap<K, V> implements TemplateModelAdapter {
     private final DefaultObjectWrapper wrapper;
     private final TemplateHashModel model;
-    private Set entrySet;
+    private Set<Map.Entry<K, V>> entrySet;
     
     TemplateHashModelAdapter(TemplateHashModel model, DefaultObjectWrapper wrapper) {
         this.model = model;
@@ -61,10 +62,15 @@ class TemplateHashModelAdapter extends AbstractMap implements TemplateModelAdapt
         }
     }
     
+    @SuppressWarnings("unchecked")
     @Override
-    public Object get(Object key) {
+    public V get(Object key) {
+        // TODO [FM3] This restriction must be removed when TemplateHashModel allows non-string keys.
+        if (!(key instanceof String)) {
+            return null; 
+        }
         try {
-            return wrapper.unwrap(model.get(String.valueOf(key)));
+            return (V) wrapper.unwrap(model.get((String) key));
         } catch (TemplateException e) {
             throw new UndeclaredThrowableException(e);
         }
@@ -89,75 +95,103 @@ class TemplateHashModelAdapter extends AbstractMap implements TemplateModelAdapt
     }
     
     @Override
-    public Set entrySet() {
+    public Set<Map.Entry<K, V>> entrySet() {
         if (entrySet != null) {
             return entrySet;
         }
-        return entrySet = new AbstractSet() {
+        return entrySet = new AbstractSet<Map.Entry<K, V>>() {
             @Override
-            public Iterator iterator() {
-                final TemplateModelIterator iterator;
+            public Iterator<Map.Entry<K, V>> iterator() {
+                final KeyValuePairIterator kvpIter;
                 try {
-                     iterator = getModelEx().keys().iterator();
+                     kvpIter = getModelEx().keyValuePairIterator();
                 } catch (TemplateException e) {
                     throw new UndeclaredThrowableException(e);
                 }
-                return new Iterator() {
+                return new Iterator<Map.Entry<K, V>>() {
                     @Override
                     public boolean hasNext() {
                         try {
-                            return iterator.hasNext();
+                            return kvpIter.hasNext();
                         } catch (TemplateException e) {
                             throw new UndeclaredThrowableException(e);
                         }
                     }
                     
                     @Override
-                    public Object next() {
-                        final Object key;
+                    public Map.Entry<K, V> next() {
+                        final KeyValuePair kvp;
                         try {
-                            if (!iterator.hasNext()) {
+                            if (!kvpIter.hasNext()) {
                                 throw new NoSuchElementException();
                             }
-                            key = wrapper.unwrap(iterator.next());
+                            kvp = kvpIter.next();
                         } catch (TemplateException e) {
                             throw new UndeclaredThrowableException(e);
                         }
-                        return new Map.Entry() {
+                        return new Map.Entry<K, V>() {
+                            private boolean keyCalculated;
+                            private K key;
+                            
+                            private boolean valueCalculated;
+                            private V value;
+                            
+                            @SuppressWarnings("unchecked")
                             @Override
-                            public Object getKey() {
-                                return key;
+                            public K getKey() {
+                                    if (!keyCalculated) {
+                                        try {
+                                            key = (K) wrapper.unwrap(kvp.getKey());;
+                                        } catch (TemplateException e) {
+                                            throw new UndeclaredThrowableException(e);
+                                        }
+                                        keyCalculated = true;
+                                    }
+                                    return key;
                             }
                             
+                            @SuppressWarnings("unchecked")
                             @Override
-                            public Object getValue() {
-                                return get(key);
+                            public V getValue() {
+                                if (!valueCalculated) {
+                                    try {
+                                        value = (V) wrapper.unwrap(kvp.getValue());;
+                                    } catch (TemplateException e) {
+                                        throw new UndeclaredThrowableException(e);
+                                    }
+                                    valueCalculated = true;
+                                }
+                                return value;
                             }
                             
                             @Override
-                            public Object setValue(Object value) {
+                            public V setValue(Object value) {
                                 throw new UnsupportedOperationException();
                             }
                             
+                            @SuppressWarnings("unchecked")
                             @Override
                             public boolean equals(Object o) {
-                                if (!(o instanceof Map.Entry))
+                                if (!(o instanceof Map.Entry)) {
                                     return false;
-                                Map.Entry e = (Map.Entry) o;
+                                }
+                                Map.Entry<K, V> e = (Map.Entry <K, V>) o;
                                 Object k1 = getKey();
                                 Object k2 = e.getKey();
                                 if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                                     Object v1 = getValue();
                                     Object v2 = e.getValue();
-                                    if (v1 == v2 || (v1 != null && v1.equals(v2))) 
+                                    if (v1 == v2 || (v1 != null && v1.equals(v2))) { 
                                         return true;
+                                    }
                                 }
                                 return false;
                             }
                         
                             @Override
                             public int hashCode() {
-                                Object value = getValue();
+                                K key = getKey();
+                                V value = getValue();
                                 return (key == null ? 0 : key.hashCode()) ^
                                        (value == null ? 0 : value.hashCode());
                             }