Repository: incubator-freemarker Updated Branches: refs/heads/3 aec8e6672 -> cece2e1cb
?keys and ?values now return a collection model (not a sequence model), similarly as Java's Map keys()/Values do return a Collection. To help handling the rare cases where one still wants them as sequences, added `?sequence`. It's the responsibility of the user to decide if the iterable value can contain too many items for that. (In FM2 such conversion could be triggered without the user realizing it, in the case of ?keys/?values return values. Now it's at least done explicitly.) Project: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/repo Commit: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/commit/cece2e1c Tree: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/tree/cece2e1c Diff: http://git-wip-us.apache.org/repos/asf/incubator-freemarker/diff/cece2e1c Branch: refs/heads/3 Commit: cece2e1cbc102127aecd3d41b50cf9089da3baa9 Parents: aec8e66 Author: ddekany <[email protected]> Authored: Tue Sep 5 13:14:31 2017 +0200 Committer: ddekany <[email protected]> Committed: Tue Sep 5 13:14:31 2017 +0200 ---------------------------------------------------------------------- FM3-CHANGE-LOG.txt | 12 +++ .../freemarker/core/SequenceBuiltInTest.java | 75 ++++++++++++++++++ .../core/cano-identifier-escaping.ftl | 2 +- .../core/cano-identifier-escaping.ftl.out | 2 +- .../expected/identifier-escaping.txt | 2 +- .../core/templatesuite/expected/macros.txt | 6 +- .../templatesuite/templates/hashliteral.ftl | 4 +- .../templates/identifier-escaping.ftl | 4 +- .../core/templatesuite/templates/macros.ftl | 2 +- .../apache/freemarker/core/ASTExpBuiltIn.java | 3 +- .../freemarker/core/ASTExpHashLiteral.java | 5 +- .../freemarker/core/BuiltInForSequence.java | 3 + .../freemarker/core/BuiltInsForHashes.java | 12 ++- .../freemarker/core/BuiltInsForSequences.java | 47 +++++++---- .../apache/freemarker/core/MessageUtils.java | 10 +++ .../apache/freemarker/core/NativeSequence.java | 4 + .../freemarker/core/model/impl/BeanModel.java | 4 +- .../core/model/impl/IterableAndSequence.java | 82 -------------------- 18 files changed, 160 insertions(+), 119 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/FM3-CHANGE-LOG.txt ---------------------------------------------------------------------- diff --git a/FM3-CHANGE-LOG.txt b/FM3-CHANGE-LOG.txt index 5e6ace9..e1bbb44 100644 --- a/FM3-CHANGE-LOG.txt +++ b/FM3-CHANGE-LOG.txt @@ -131,17 +131,29 @@ Node: Changes already mentioned above aren't repeated here! - It's not tolerated anymore if the caller of a macro has declared more nested content parameters than what `#nested` passes to it (like in `<#macro m><#nested 1, 2></#macro> <@m ; i, j, k>...</@>`, where `k` has no corresponding value in `#nested`). + (The template converter tool can't do this conversion.) - In `#macro` (and `function`) named parameters with default values need not be at the end of the parameter list anymore. (Positional parameters with default values need to be at the end of the list of positional parameters only.) - When parameter default expressions are evaluated, only the parameters defined earlier are already set. So `<#macro m a b=a>` works, but `<#macro m a=b b>` won't work anymore (unless there's `b` outside the parameter list), as `b` is not yet set when the default of `a` is calculated. + (The template converter tool can't do this conversion.) - Built-ins don't convert their parameters to string anymore; in FM some (not all) of them did, because they were based on the TemplateMethod interface (deprecated even in FM2) that required String paramteters and was auto-converted by FM2. (The template converter tool can't do this conversion.) - The string ranges like `someString[1..0]` (that is, where the end is one less than the beginning) aren't tolerated anymore, and are errors. (The template converter tool can't do this conversion.) +- Added `?sequence`, which converts an iterable value into a sequence. This is to be used with care, as iterables + that are not also sequences sometimes contain a large number of items, and the resulting sequence will have to + store all of them. +- `?keys` and `?values` (used to return the keys and values in a hash) now returns a collection, not a sequence. + Note that for FM3 collections ?size works, but not accessing by index. This is to be more aligned with the + capabilities of java.util.Map. In case you know that you won't have a too large number of entries, and you need + a sequence of the keys or values, use `myMap?keys?sequence` or `myMap?values?sequence`. + (The template converter tool can't do this conversion. Because `?sequence` is a relatively expensive operation, + it shouldn't be invoked for multiple times on the same value, instead the result should be extracted into a + variable, like: `<#assign keySeq = keys?sequence>[... Do multiple things with keySeq here ...]`. Java API changes http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java new file mode 100644 index 0000000..0809d9e --- /dev/null +++ b/freemarker-core-test/src/test/java/org/apache/freemarker/core/SequenceBuiltInTest.java @@ -0,0 +1,75 @@ +/* + * 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; + +import static org.hamcrest.Matchers.*; +import static org.junit.Assert.*; + +import java.io.IOException; + +import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.impl.SimpleCollection; +import org.apache.freemarker.core.model.impl.SimpleIterable; +import org.apache.freemarker.test.TemplateTest; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +public class SequenceBuiltInTest extends TemplateTest { + + @Test + public void testWithIterable() throws TemplateException, IOException { + TemplateModel xs = new SimpleIterable(ImmutableList.of("a", "b"), getConfiguration().getObjectWrapper()); + assertThat(xs, not(instanceOf(TemplateCollectionModel.class))); + addToDataModel("xs", xs); + + try { + assertOutput("${xs[1]}", "b"); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), containsString("?sequence")); // Contains tip to use ?sequence + } + assertOutput("${xs?sequence[1]}", "b"); + } + + @Test + public void testWithCollection() throws TemplateException, IOException { + TemplateModel xs = new SimpleCollection(ImmutableList.of("a", "b"), getConfiguration().getObjectWrapper()); + assertThat(xs, not(instanceOf(TemplateSequenceModel.class))); + addToDataModel("xs", xs); + + try { + assertOutput("${xs[1]}", "b"); + fail(); + } catch (TemplateException e) { + assertThat(e.getMessage(), containsString("?sequence")); // Contains tip to use ?sequence + } + assertOutput("${xs?sequence[1]}", "b"); + } + + @Test + public void testWithSequence() throws TemplateException, IOException { + // As it returns the sequence as is, it works with an infinite sequence: + assertOutput("${(11..)?sequence[1]}", "12"); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl index 41923cc..c866a06 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl @@ -69,7 +69,7 @@ ${.vars['as"d']} <#global g\-a=1 g\-b=2 "g-c"=3> <#macro dumpNS> - <#list .namespace?keys?sort as k> + <#list .namespace?keys?sequence?sort as k> ${k} = <#local v = .namespace[k]><#if v?isString>${v}<#else>...</#if><#lt> </#list> </#macro> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out index 05dc62d..f50dedb 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/cano-identifier-escaping.ftl.out @@ -40,5 +40,5 @@ ${hash["--moz-prop"]} <#assign "as'c" = "as4">${.vars["as'c"]} <#assign 'as"d' = "as5">${.vars['as"d']} -<#global g\-a = 1, g\-b = 2, g\-c = 3><#macro dumpNS><#list .namespace?keys?sort as k>${k} = <#local v = .namespace[k]><#if v?isString>${v}<#else>...</#if> +<#global g\-a = 1, g\-b = 2, g\-c = 3><#macro dumpNS><#list .namespace?keys?sequence?sort as k>${k} = <#local v = .namespace[k]><#if v?isString>${v}<#else>...</#if> </#list></#macro><@dumpNS/> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt index 1c62bd5..6f7ba21 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/identifier-escaping.txt @@ -38,7 +38,7 @@ as3 as4 as5 -<catchAll x=1 y=2 a:b.c=5 data-foo=4 z=3 /> +<catchAll x=1 y=2 z=3 data-foo=4 a:b.c=5 /> ---.: = dash-dash-dash etc. @as@_a = as1 http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt index 680a08e..11d860f 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/expected/macros.txt @@ -56,9 +56,9 @@ <p>Test "catch-all" macro parameter:</p> -foo=a baz=[] -foo=a baz=[bar=b] -foo=a baz=[bar=b, baz=c] +foo=a bar=[] +foo=a bar=[bar=b] +foo=a bar=[bar=b, baz=c] Hello World! Today is Monday. http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl index dc3c972..463e41b 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/hashliteral.ftl @@ -80,8 +80,8 @@ ${test["1"]} <p>Hash concatenation:</p> <#assign cc = { "a" : 1, "b" : 2 } + { "b" : 3, "c" : 4 }> -<#list cc?keys?sort as key> -${key} => ${cc[key]} +<#list cc as key, value> +${key} => ${value} </#list> <p>Empty hash concatenation:</p> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl index b221deb..1d472a7 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/identifier-escaping.ftl @@ -69,12 +69,12 @@ ${.vars['as"d']} <#global g\-a=1 g\-b=2 "g-c"=3> <#macro catchAll x y attrs...> -<catchAll x=${x} y=${y}<#list attrs?keys?sort as k> ${k}=${attrs[k]}</#list> /> +<catchAll x=${x} y=${y}<#list attrs as k, v> ${k}=${v}</#list> /> </#macro> <@catchAll x=1 y=2 z=3 data\-foo=4 a\:b\.c=5 /> <#macro dumpNS> - <#list .namespace?keys?sort as k> + <#list .namespace?keys?sequence?sort as k> ${k} = <#local v = .namespace[k]><#if v?isString>${v}<#else>...</#if><#lt> </#list> </#macro> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl index 181326c..7ef6395 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/macros.ftl @@ -75,7 +75,7 @@ <p>Test "catch-all" macro parameter:</p> <#macro catch\-all foo bar...> -foo=${foo} baz=[<#list bar?keys?sort as key>${key}=${bar[key]}<#if key_has_next>, </#if></#list>] +foo=${foo} bar=[<#list bar as k, v>${k}=${v}<#sep>, </#list>] </#macro> <#assign catchall = .namespace["catch-all"]> http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java index b0aa3b0..322d662 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltIn.java @@ -75,7 +75,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { protected ASTExpression target; protected String key; - static final int NUMBER_OF_BIS = 262; + static final int NUMBER_OF_BIS = 263; static final HashMap<String, ASTExpBuiltIn> BUILT_INS_BY_NAME = new HashMap(NUMBER_OF_BIS * 3 / 2 + 1, 1f); static { @@ -247,6 +247,7 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { putBI("removeEnding", new BuiltInsForStringsBasic.remove_endingBI()); putBI("removeBeginning", new BuiltInsForStringsBasic.remove_beginningBI()); putBI("rtf", new BuiltInsForStringsEncoding.rtfBI()); + putBI("sequence", new BuiltInsForSequences.sequenceBI()); putBI("seqContains", new seq_containsBI()); putBI("seqIndexOf", new seq_index_ofBI(true)); putBI("seqLastIndexOf", new seq_index_ofBI(false)); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/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 dd29369..7198f6a 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 @@ -28,7 +28,6 @@ import org.apache.freemarker.core.model.TemplateCollectionModel; import org.apache.freemarker.core.model.TemplateHashModelEx2; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.model.impl.IterableAndSequence; /** * AST expression node: <tt>{ keyExp: valueExp, ... }</tt> @@ -130,7 +129,7 @@ final class ASTExpHashLiteral extends ASTExpression { @Override public TemplateCollectionModel keys() { if (keyCollection == null) { - keyCollection = new IterableAndSequence(new NativeStringCollectionCollection(map.keySet())); + keyCollection = new NativeStringCollectionCollection(map.keySet()); } return keyCollection; } @@ -138,7 +137,7 @@ final class ASTExpHashLiteral extends ASTExpression { @Override public TemplateCollectionModel values() { if (valueCollection == null) { - valueCollection = new IterableAndSequence(new NativeCollection(map.values())); + valueCollection = new NativeCollection(map.values()); } return valueCollection; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java index cb7cc73..8306b8f 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForSequence.java @@ -22,6 +22,9 @@ package org.apache.freemarker.core; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateSequenceModel; +/** + * Built-in that meant to acts on {@link TemplateSequenceModel}-s only. + */ abstract class BuiltInForSequence extends ASTExpBuiltIn { @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java index 4d2a9d5..eaf12f5 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForHashes.java @@ -19,11 +19,9 @@ package org.apache.freemarker.core; -import org.apache.freemarker.core.model.TemplateIterableModel; +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.TemplateSequenceModel; -import org.apache.freemarker.core.model.impl.IterableAndSequence; /** * A holder for builtins that operate exclusively on hash left-hand value. @@ -35,9 +33,9 @@ class BuiltInsForHashes { @Override TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env) throws TemplateException, InvalidReferenceException { - TemplateIterableModel keys = hashExModel.keys(); + TemplateCollectionModel keys = hashExModel.keys(); if (keys == null) throw newNullPropertyException("keys", hashExModel, env); - return keys instanceof TemplateSequenceModel ? keys : new IterableAndSequence(keys); + return keys; } } @@ -46,9 +44,9 @@ class BuiltInsForHashes { @Override TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env) throws TemplateException, InvalidReferenceException { - TemplateIterableModel values = hashExModel.values(); + TemplateCollectionModel values = hashExModel.values(); if (values == null) throw newNullPropertyException("values", hashExModel, env); - return values instanceof TemplateSequenceModel ? values : new IterableAndSequence(values); + return values; } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java index c28b385..6b8be3d 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForSequences.java @@ -31,6 +31,7 @@ import java.util.Date; import org.apache.freemarker.core.arithmetic.ArithmeticEngine; import org.apache.freemarker.core.model.ArgumentArrayLayout; 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.TemplateFunctionModel; import org.apache.freemarker.core.model.TemplateHashModel; @@ -51,11 +52,12 @@ import org.apache.freemarker.core.util._StringUtils; * A holder for builtins that operate on sequence (or some even on iterable) left-hand value. */ class BuiltInsForSequences { - + + // TODO [FM3] Should work on TemplateIterableModel as well static class chunkBI extends BuiltInForSequence { private class BIMethod extends BuiltInCallableImpl implements TemplateFunctionModel { - + private final TemplateSequenceModel tsm; private BIMethod(TemplateSequenceModel tsm) { @@ -79,15 +81,15 @@ class BuiltInsForSequences { } private static class ChunkedSequence implements TemplateSequenceModel { - + private final TemplateSequenceModel wrappedTsm; - + private final int chunkSize; - + private final TemplateModel fillerItem; - + private final int numberOfChunks; - + private ChunkedSequence( TemplateSequenceModel wrappedTsm, int chunkSize, TemplateModel fillerItem) throws TemplateException { @@ -103,9 +105,9 @@ class BuiltInsForSequences { if (chunkIndex >= numberOfChunks || chunkIndex < 0) { return null; } - + return new TemplateSequenceModel() { - + private final int baseIndex = chunkIndex * chunkSize; @Override @@ -155,16 +157,16 @@ class BuiltInsForSequences { return new SequenceTemplateModelIterator(this); } } - + @Override TemplateModel calculateResult(TemplateSequenceModel tsm) throws TemplateException { return new BIMethod(tsm); } - + } - + static class firstBI extends BuiltInForIterable { - + @Override TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException { TemplateModelIterator iter = model.iterator(); @@ -814,6 +816,25 @@ class BuiltInsForSequences { } } + static class sequenceBI extends BuiltInForIterable { + + @Override + TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException { + if (model instanceof TemplateSequenceModel) { + return model; + } + NativeSequence seq = + model instanceof TemplateCollectionModel + ? new NativeSequence(((TemplateCollectionModel) model).getCollectionSize()) + : new NativeSequence(); + for (TemplateModelIterator iter = model.iterator(); iter.hasNext(); ) { + seq.add(iter.next()); + } + return seq; + } + + } + // Can't be instantiated private BuiltInsForSequences() { } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java index 7b9952e..515b59b 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/MessageUtils.java @@ -19,11 +19,15 @@ package org.apache.freemarker.core; +import java.util.Arrays; + import org.apache.freemarker.core.model.TemplateBooleanModel; import org.apache.freemarker.core.model.TemplateDateModel; +import org.apache.freemarker.core.model.TemplateIterableModel; import org.apache.freemarker.core.model.TemplateMarkupOutputModel; 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.BugException; import org.apache.freemarker.core.util.TemplateLanguageUtils; @@ -344,6 +348,12 @@ class MessageUtils { errorDescBuilder.tip(tip); } } + if (model instanceof TemplateIterableModel && Arrays.asList(expectedTypes).contains(TemplateSequenceModel + .class)) { + errorDescBuilder.tip("As the problematic value contains a collection of items, so you could covert it " + + "to a sequence like someValue?sequence. Be sure that you won't have a large number of items " + + "though, as all will be held in memory at once."); + } return errorDescBuilder; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java index b5febb8..9f7af3a 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/NativeSequence.java @@ -38,6 +38,10 @@ class NativeSequence implements TemplateSequenceModel, Serializable { private final ArrayList<TemplateModel> items; + public NativeSequence() { + items = new ArrayList<>(); + } + public NativeSequence(int capacity) { items = new ArrayList<>(capacity); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/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 3f4c45b..accadaf 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 @@ -289,7 +289,7 @@ public class BeanModel @Override public TemplateCollectionModel keys() { - return new IterableAndSequence(DefaultNonListCollectionAdapter.adapt(keySet(), wrapper)); + return DefaultNonListCollectionAdapter.adapt(keySet(), wrapper); } @Override @@ -300,7 +300,7 @@ public class BeanModel String key = ((TemplateStringModel) it.next()).getAsString(); values.add(get(key)); } - return new IterableAndSequence(DefaultNonListCollectionAdapter.adapt(values, wrapper)); + return DefaultNonListCollectionAdapter.adapt(values, wrapper); } @Override http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cece2e1c/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java b/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java deleted file mode 100644 index eb51098..0000000 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/model/impl/IterableAndSequence.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * 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 java.util.ArrayList; - -import org.apache.freemarker.core.TemplateException; -import org.apache.freemarker.core.model.TemplateIterableModel; -import org.apache.freemarker.core.model.TemplateCollectionModel; -import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateModelIterator; -import org.apache.freemarker.core.model.TemplateSequenceModel; - -/** - * Add sequence capabilities to an existing iterable. Used by ?keys and ?values built-ins. - */ -// TODO [FM3] Maybe ?keys/?values should just return a TemplateCollectionModel -final public class IterableAndSequence implements TemplateSequenceModel { - private TemplateIterableModel iterable; - private ArrayList<TemplateModel> data; - - public IterableAndSequence(TemplateIterableModel iterable) { - this.iterable = iterable; - } - - @Override - public TemplateModelIterator iterator() throws TemplateException { - return iterable.iterator(); - } - - @Override - public TemplateModel get(int i) throws TemplateException { - initSequence(); - return i < data.size() && i >= 0 ? data.get(i) : null; - } - - @Override - public int getCollectionSize() throws TemplateException { - if (iterable instanceof TemplateCollectionModel) { - return ((TemplateCollectionModel) iterable).getCollectionSize(); - } else { - initSequence(); - return data.size(); - } - } - - @Override - public boolean isEmptyCollection() throws TemplateException { - if (iterable instanceof TemplateCollectionModel) { - return ((TemplateCollectionModel) iterable).isEmptyCollection(); - } else { - return iterable.iterator().hasNext(); - } - } - - private void initSequence() throws TemplateException { - if (data == null) { - data = new ArrayList<>(); - TemplateModelIterator it = iterable.iterator(); - while (it.hasNext()) { - data.add(it.next()); - } - } - } -}
