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 <[email protected]> Authored: Fri Oct 20 01:23:14 2017 +0200 Committer: ddekany <[email protected]> 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();
