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 <ddek...@apache.org> Authored: Thu Mar 1 17:10:04 2018 +0100 Committer: ddekany <ddek...@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()); }