http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl index 18c035d..738dc76 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/range.ftl @@ -16,7 +16,308 @@ specific language governing permissions and limitations under the License. --> -<#include 'range-common.ftl'> +<#-- A version of "?join" that fails at null-s in the sequence: --> +<#function join(seq, sep='')> + <#local r = ""> + <#list seq as i> + <#local r = r + i> + <#if i_has_next> + <#local r = r + sep> + </#if> + </#list> + <#return r> +</#function> + +<#-----------------------> +<#-- Range expressions --> + +<@assertEquals actual=join(1..2, ' ') expected="1 2" /> +<@assertEquals actual=join(1..1, ' ') expected="1" /> +<@assertEquals actual=join(1..0, ' ') expected="1 0" /> +<@assertEquals actual=join(1..-1, ' ') expected="1 0 -1" /> +<@assertEquals actual=join(-1..-1, ' ') expected="-1" /> +<@assertEquals actual=join(-1..1, ' ') expected="-1 0 1" /> + +<@assertEquals actual=join(1..<3, ' ') expected="1 2" /> +<@assertEquals actual=join(1..<2, ' ') expected="1" /> +<@assertEquals actual=join(1..<1, ' ') expected="" /> +<@assertEquals actual=join(1..<0, ' ') expected="1" /> +<@assertEquals actual=join(1..<-1, ' ') expected="1 0" /> +<@assertEquals actual=join(1..<-2, ' ') expected="1 0 -1" /> +<@assertEquals actual=join(-1..<0, ' ') expected="-1" /> +<@assertEquals actual=join(-1..<2, ' ') expected="-1 0 1" /> + +<@assertEquals actual=join(1..!3, ' ') expected="1 2" /> +<@assertEquals actual=join(1..!2, ' ') expected="1" /> +<@assertEquals actual=join(1..!1, ' ') expected="" /> +<@assertEquals actual=join(1..!0, ' ') expected="1" /> +<@assertEquals actual=join(1..!-1, ' ') expected="1 0" /> +<@assertEquals actual=join(1..!-2, ' ') expected="1 0 -1" /> +<@assertEquals actual=join(-1..!0, ' ') expected="-1" /> +<@assertEquals actual=join(-1..!2, ' ') expected="-1 0 1" /> + +<@assertEquals actual=join(1..*2, ' ') expected="1 2" /> +<@assertEquals actual=join(1..*1, ' ') expected="1" /> +<@assertEquals actual=join(1..*0, ' ') expected="" /> +<@assertEquals actual=join(1..*-1, ' ') expected="1" /> +<@assertEquals actual=join(1..*-2, ' ') expected="1 0" /> +<@assertEquals actual=join(1..*-3, ' ') expected="1 0 -1" /> +<@assertEquals actual=join(-1..*1, ' ') expected="-1" /> +<@assertEquals actual=join(-1..*3, ' ') expected="-1 0 1" /> + +<@assertEquals actual=1 expected=(0..0)?size /> +<@assertEquals actual=1 expected=(1..1)?size /> +<@assertEquals actual=1 expected=(2..2)?size /> +<@assertEquals actual=2 expected=(0..1)?size /> +<@assertEquals actual=2 expected=(1..2)?size /> +<@assertEquals actual=2 expected=(2..3)?size /> +<@assertEquals actual=3 expected=(2..4)?size /> +<@assertEquals actual=2 expected=(1..0)?size /> +<@assertEquals actual=2 expected=(2..1)?size /> +<@assertEquals actual=2 expected=(3..2)?size /> +<@assertEquals actual=3 expected=(4..2)?size /> + +<@assertEquals actual=0 expected=(0..<0)?size /> +<@assertEquals actual=0 expected=(1..<1)?size /> +<@assertEquals actual=0 expected=(2..<2)?size /> +<@assertEquals actual=1 expected=(0..<1)?size /> +<@assertEquals actual=1 expected=(1..<2)?size /> +<@assertEquals actual=1 expected=(2..<3)?size /> +<@assertEquals actual=2 expected=(2..<4)?size /> +<@assertEquals actual=1 expected=(1..<0)?size /> +<@assertEquals actual=1 expected=(2..<1)?size /> +<@assertEquals actual=1 expected=(3..<2)?size /> +<@assertEquals actual=2 expected=(4..<2)?size /> + +<@assertEquals actual=0 expected=(0..*0)?size /> +<@assertEquals actual=0 expected=(1..*0)?size /> +<@assertEquals actual=0 expected=(2..*0)?size /> +<@assertEquals actual=1 expected=(0..*1)?size /> +<@assertEquals actual=1 expected=(1..*1)?size /> +<@assertEquals actual=1 expected=(2..*1)?size /> +<@assertEquals actual=2 expected=(2..*2)?size /> +<@assertEquals actual=1 expected=(0..*-1)?size /> +<@assertEquals actual=1 expected=(1..*-1)?size /> +<@assertEquals actual=1 expected=(2..*-1)?size /> +<@assertEquals actual=2 expected=(0..*-2)?size /> +<@assertEquals actual=2 expected=(1..*-2)?size /> +<@assertEquals actual=2 expected=(2..*-2)?size /> + + +<#---------------------> +<#-- String slicing: --> + +<#assign s = 'abcd'> + +<@assertEquals actual=s[0..] expected="abcd" /> +<@assertEquals actual=s[1..] expected="bcd" /> +<@assertEquals actual=s[2..] expected="cd" /> +<@assertEquals actual=s[3..] expected="d" /> +<@assertEquals actual=s[4..] expected="" /> +<@assertFails message="5 is out of bounds"> + <#assign _ = s[5..] /> +</@assertFails> +<@assertFails message="6 is out of bounds"> + <#assign _ = s[6..] /> +</@assertFails> + +<@assertEquals actual=s[1..2] expected="bc" /> +<@assertEquals actual=s[1..1] expected="b" /> +<@assertEquals actual=s[0..1] expected="ab" /> +<@assertEquals actual=s[0..0] expected="a" /> +<@assertFails message="4 is out of bounds"> + <#assign _ = s[1..4] /> +</@assertFails> +<@assertFails message="5 is out of bounds"> + <#assign _ = s[1..5] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[-1..1] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[-2..1] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[0..-1] /> +</@assertFails> + +<@assertEquals actual=s[1..<3] expected="bc" /> +<@assertEquals actual=s[1..!3] expected="bc" /> +<@assertEquals actual=s[1..<2] expected="b" /> +<@assertEquals actual=s[1..<0] expected="b" /> +<@assertEquals actual=s[1..<1] expected="" /> +<@assertEquals actual=s[0..<0] expected="" /> +<@assertEquals actual=s[5..<5] expected="" /> +<@assertEquals actual=s[6..<6] expected="" /> +<@assertEquals actual=s[-5..<-5] expected="" /> +<@assertFails message="negative"> + <#assign _ = s[-5..<1] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[2..<-4] /> +</@assertFails> +<@assertFails message="decreasing"> + <#assign _ = s[2..<0] /> +</@assertFails> + +<@assertEquals actual=s[1..*-1] expected="b" /> +<@assertEquals actual=s[1..*0] expected="" /> +<@assertEquals actual=s[1..*1] expected="b" /> +<@assertEquals actual=s[1..*2] expected="bc" /> +<@assertEquals actual=s[1..*3] expected="bcd" /> +<@assertEquals actual=s[1..*4] expected="bcd" /> +<@assertEquals actual=s[1..*5] expected="bcd" /> +<@assertEquals actual=s[4..*1] expected="" /> +<@assertEquals actual=s[5..*0] expected="" /> +<@assertEquals actual=s[6..*0] expected="" /> +<@assertEquals actual=s[-5..*0] expected="" /> +<@assertEquals actual=s[0..*0] expected="" /> +<@assertEquals actual=s[0..*-1] expected="a" /> +<@assertEquals actual=s[0..*-2] expected="a" /> +<@assertEquals actual=s[0..*-3] expected="a" /> +<@assertFails message="5 is out of bounds"> + <#assign _ = s[5..*1] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[-1..*1] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[-2..*1] /> +</@assertFails> +<@assertFails message="decreasing"> + <#assign _ = s[1..*-2] /> +</@assertFails> +<@assertFails message="decreasing"> + <#assign _ = s[1..*-3] /> +</@assertFails> +<@assertFails message="4 is out of bounds"> + <#assign _ = s[4..*-1] /> +</@assertFails> + +<#-- FreeMarker 2 string backward-range bug not supported anymore: --> +<@assertFails message="decreasing"> + <#assign _ = s[1..0] /> +</@assertFails> +<@assertFails message="decreasing"> + <#assign _ = s[2..1] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[0..-1] /> +</@assertFails> +<@assertFails message="decreasing"> + <#assign _ = s[3..1] /> +</@assertFails> +<#-- The bug was never emulated for operators introduced after 2.3.20: --> +<@assertFails message="decreasing"> + <#assign _ = s[3..<1] /> +</@assertFails> +<@assertFails message="decreasing"> + <#assign _ = s[3..*-2] /> +</@assertFails> + +<#assign r = 1..2> +<@assertEquals actual=s[r] expected="bc" /> +<#assign r = 2..1> +<@assertFails message="decreasing"> + <#assign _ = s[r] /> +</@assertFails> +<#assign r = 1..<2> +<@assertEquals actual=s[r] expected="b" /> +<#assign r = 2..<4> +<@assertEquals actual=s[r] expected="cd" /> +<#assign r = 2..> +<@assertEquals actual=s[r] expected="cd" /> +<#assign r = 1..*2> +<@assertEquals actual=s[r] expected="bc" /> + +<#-----------------------> +<#-- Sequence slicing: --> + +<#assign s = ['a', 'b', 'c', 'd']> + +<@assertEquals actual=join(s[0..]) expected="abcd" /> +<@assertEquals actual=join(s[1..]) expected="bcd" /> +<@assertEquals actual=join(s[2..]) expected="cd" /> +<@assertEquals actual=join(s[3..]) expected="d" /> +<@assertEquals actual=join(s[4..]) expected="" /> +<@assertFails message="5 is out of bounds"> + <#assign _ = s[5..] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[-1..] /> +</@assertFails> + +<@assertEquals actual=join(s[1..2]) expected="bc" /> +<@assertEquals actual=join(s[1..1]) expected="b" /> +<@assertEquals actual=join(s[0..1]) expected="ab" /> +<@assertEquals actual=join(s[0..0]) expected="a" /> +<@assertFails message="5 is out of bounds"> + <#assign _ = s[1..5] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[-1..0] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[0..-1] /> +</@assertFails> + +<@assertEquals actual=join(s[1..<3]) expected="bc" /> +<@assertEquals actual=join(s[1..!3]) expected="bc" /> +<@assertEquals actual=join(s[1..<2]) expected="b" /> +<@assertEquals actual=join(s[1..<0]) expected="b" /> +<@assertEquals actual=join(s[1..<1]) expected="" /> +<@assertEquals actual=join(s[0..<0]) expected="" /> + +<@assertEquals actual=join(s[1..0]) expected="ba" /> +<@assertEquals actual=join(s[2..1]) expected="cb" /> +<@assertEquals actual=join(s[2..0]) expected="cba" /> +<@assertEquals actual=join(s[2..<0]) expected="cb" /> +<@assertEquals actual=join(s[1..<0]) expected="b" /> +<@assertEquals actual=join(s[0..<0]) expected="" /> +<@assertEquals actual=join(s[3..<1]) expected="dc" /> +<@assertEquals actual=join(s[2..<1]) expected="c" /> +<@assertEquals actual=join(s[1..<1]) expected="" /> +<@assertEquals actual=join(s[0..<1]) expected="a" /> +<@assertEquals actual=join(s[0..<0]) expected="" /> +<@assertEquals actual=join(s[5..<5]) expected="" /> +<@assertEquals actual=join(s[-5..<-5]) expected="" /> + +<@assertEquals actual=join(s[0..*-4]) expected="a" /> +<@assertEquals actual=join(s[1..*-4]) expected="ba" /> +<@assertEquals actual=join(s[1..*-3]) expected="ba" /> +<@assertEquals actual=join(s[1..*-2]) expected="ba" /> +<@assertEquals actual=join(s[1..*-1]) expected="b" /> +<@assertEquals actual=join(s[1..*0]) expected="" /> +<@assertEquals actual=join(s[1..*1]) expected="b" /> +<@assertEquals actual=join(s[1..*2]) expected="bc" /> +<@assertEquals actual=join(s[1..*3]) expected="bcd" /> +<@assertEquals actual=join(s[1..*4]) expected="bcd" /> +<@assertEquals actual=join(s[1..*5]) expected="bcd" /> +<@assertEquals actual=join(s[0..*3]) expected="abc" /> +<@assertEquals actual=join(s[2..*3]) expected="cd" /> +<@assertEquals actual=join(s[3..*3]) expected="d" /> +<@assertEquals actual=join(s[4..*3]) expected="" /> +<@assertFails message="5 is out of bounds"> + <#assign _ = s[5..*3] /> +</@assertFails> +<@assertFails message="negative"> + <#assign _ = s[-1..*2] /> +</@assertFails> + +<#assign r = 1..2> +<@assertEquals actual=join(s[r]) expected="bc" /> +<#assign r = 2..0> +<@assertEquals actual=join(s[r]) expected="cba" /> +<#assign r = 1..<2> +<@assertEquals actual=join(s[r]) expected="b" /> +<#assign r = 2..<0> +<@assertEquals actual=join(s[r]) expected="cb" /> +<#assign r = 2..> +<@assertEquals actual=join(s[r]) expected="cd" /> +<#assign r = 1..*2> +<@assertEquals actual=join(s[r]) expected="bc" /> +<#assign r = 1..*-9> +<@assertEquals actual=join(s[r]) expected="ba" /> <@assertEquals actual=(4..)?size expected=2147483647 /> <@assertEquals actual=limitedJoin(4.., 3) expected="4, 5, 6, ..." /> @@ -24,9 +325,8 @@ <@assertEquals actual=(4..)[0] expected=4 /> <@assertEquals actual=(4..)[1] expected=5 /> <@assertEquals actual=(4..)[1000000] expected=1000004 /> -<@assertFails message="out of bounds"> - <@assertEquals actual=(4..)[-1] expected=5 /> -</@> +<@assert !(4..)[-1]?? /> +<@assert !(1..2)[2]?? /> <#assign r = 2147483646..> <@assertEquals actual=r?size expected=2147483647 />
http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl ---------------------------------------------------------------------- diff --git a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl index 3442824..489bd2f 100644 --- a/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl +++ b/freemarker-core-test/src/test/resources/org/apache/freemarker/core/templatesuite/templates/type-builtins.ftl @@ -17,12 +17,12 @@ under the License. --> <#setting booleanFormat="1,0"> -StNuBoHaHxSeCoCxEnInFuDiNo +StNuBoHaHxItCoSeFuDiNo <#list [ "a", 1, false, - testfunction, testmacro, - {"a":1}, [1], testcollection, testcollectionEx, - testnode, + testFunction, testMacro, + {"a":1}, [1], testIterable, testCollection, + testNode, bean, bean.m, bean.mOverloaded ] as x> ${x?isString} <#t> @@ -30,13 +30,11 @@ StNuBoHaHxSeCoCxEnInFuDiNo ${x?isBoolean} <#t> ${x?isHash} <#t> ${x?isHashEx} <#t> - ${x?isSequence} <#t> + ${x?isIterable} <#t> ${x?isCollection} <#t> - ${x?isCollectionEx} <#t> - ${x?isEnumerable} <#t> - ${x?isIndexable} <#t> + ${x?isSequence} <#t> ${x?isFunction} <#t> ${x?isDirective} <#t> ${x?isNode}<#lt> </#list> -<#macro testmacro></#macro> \ No newline at end of file +<#macro testMacro></#macro> \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 b795ca1..1d0927f 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 @@ -25,15 +25,14 @@ import java.util.Collection; import java.util.Collections; import org.apache.freemarker.core.model.TemplateBooleanModel; -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.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; import org.apache.freemarker.core.model.TemplateStringModel; -import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.model.impl.SimpleNumber; import org.apache.freemarker.core.util._StringUtils; @@ -50,7 +49,7 @@ final class ASTDirList extends ASTDirective { /** * @param listedExp - * a variable referring to a sequence or collection or extended hash to list + * a variable referring to an iterable or extended hash that we want to list * @param nestedContentParamName * The name of the variable that will hold the value of the current item when looping through listed value, * or {@code null} if we have a nested {@code #items}. If this is a hash listing then this variable will holds the value @@ -252,15 +251,15 @@ final class ASTDirList extends ASTDirective { private boolean executeNestedContent(Environment env, ASTElement[] childBuffer) throws TemplateException, IOException { return !hashListing - ? executedNestedContentForCollOrSeqListing(env, childBuffer) + ? executedNestedContentForIterableListing(env, childBuffer) : executedNestedContentForHashListing(env, childBuffer); } - private boolean executedNestedContentForCollOrSeqListing(Environment env, ASTElement[] childBuffer) + private boolean executedNestedContentForIterableListing(Environment env, ASTElement[] childBuffer) throws IOException, TemplateException { final boolean listNotEmpty; - if (listedValue instanceof TemplateCollectionModel) { - final TemplateCollectionModel collModel = (TemplateCollectionModel) listedValue; + if (listedValue instanceof TemplateIterableModel) { + final TemplateIterableModel collModel = (TemplateIterableModel) listedValue; final TemplateModelIterator iterModel = openedIterator == null ? collModel.iterator() : ((TemplateModelIterator) openedIterator); @@ -279,31 +278,12 @@ final class ASTDirList extends ASTDirective { } openedIterator = null; } else { - // We must reuse this later, because TemplateCollectionModel-s that wrap an Iterator only + // We must reuse this later, because TemplateIterableModel-s that wrap an Iterator only // allow one iterator() call. openedIterator = iterModel; env.visit(childBuffer); } } - } else if (listedValue instanceof TemplateSequenceModel) { - final TemplateSequenceModel seqModel = (TemplateSequenceModel) listedValue; - final int size = seqModel.size(); - listNotEmpty = size != 0; - if (listNotEmpty) { - if (nestedContentParam1Name != null) { - try { - for (index = 0; index < size; index++) { - nestedContentParam = seqModel.get(index); - hasNext = (size > index + 1); - env.visit(childBuffer); - } - } catch (ASTDirBreak.Break br) { - // Silently exit loop - } - } else { - env.visit(childBuffer); - } - } } else if (listedValue instanceof TemplateHashModelEx) { throw new TemplateException(env, new _ErrorDescriptionBuilder("The value you try to list is ", @@ -314,8 +294,8 @@ final class ASTDirList extends ASTDirective { } else { throw MessageUtils.newUnexpectedOperandTypeException( listedExp, listedValue, - MessageUtils.SEQUENCE_OR_COLLECTION, - MessageUtils.EXPECTED_TYPES_SEQUENCE_OR_COLLECTION, + MessageUtils.EXPECTED_TYPE_ITERABLE_DESC, + TemplateIterableModel.class, null, env); } return listNotEmpty; @@ -390,8 +370,7 @@ final class ASTDirList extends ASTDirective { } } } - } else if (listedValue instanceof TemplateCollectionModel - || listedValue instanceof TemplateSequenceModel) { + } else if (listedValue instanceof TemplateIterableModel) { throw new TemplateException(env, new _ErrorDescriptionBuilder("The value you try to list is ", new _DelayedAOrAn(new _DelayedTemplateLanguageTypeDescription(listedValue)), http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java index 645aff4..786bfcf 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirMacroOrFunction.java @@ -26,6 +26,7 @@ import java.util.List; import org.apache.freemarker.core.model.ArgumentArrayLayout; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelIterator; +import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.util.StringToIndexMap; import org.apache.freemarker.core.util._StringUtils; @@ -290,7 +291,7 @@ final class ASTDirMacroOrFunction extends ASTDirective implements TemplateModel public Collection<String> getLocalVariableNames() throws TemplateException { HashSet<String> result = new HashSet<>(); for (TemplateModelIterator it = localVars.keys().iterator(); it.hasNext(); ) { - result.add(it.next().toString()); + result.add(((TemplateStringModel) it.next()).getAsString()); } return result; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java index a849aaf..25344e6 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTDirNested.java @@ -45,10 +45,10 @@ final class ASTDirNested extends ASTDirective { ASTElement[] accept(Environment env) throws IOException, TemplateException { CallPlace macroCallPlace = env.getCurrentMacroContext().callPlace; - // When nestedContParamCnt < nestedContentParameters.size(), then we just skip calculating the extra parameters, - // and CallPlace.executeNestedContent will be successful. Note sure if this lenient behavior is a good idea, - // but for now it's inherited from FM2, so TODO [FM3]. - // When nestedContParamCnt > nestedContentParameters.size(), then later + // When nestedContParamCnt < nestedContentParameters.getCollectionSize(), then we just skip calculating the + // extra parameters, and CallPlace.executeNestedContent will be successful. Note sure if this lenient + // behavior is a good idea, but for now it's inherited from FM2, so TODO [FM3]. + // When nestedContParamCnt > nestedContentParameters.getCollectionSize(), then later // CallPlace.executeNestedContent will throw exception, but we let that happen so that the error message // generation remains centralized. (In FM2 not even this was an error.) TemplateModel[] nestedContParamValues; http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 a076d7e..5fa8eac 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 @@ -23,16 +23,15 @@ import java.util.HashSet; import java.util.Set; import org.apache.freemarker.core.arithmetic.ArithmeticEngine; -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.TemplateMarkupOutputModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateModelIterator; 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.impl.CollectionAndSequence; +import org.apache.freemarker.core.model.TemplateStringModel; import org.apache.freemarker.core.model.impl.SimpleNumber; import org.apache.freemarker.core.model.impl.SimpleString; @@ -122,9 +121,9 @@ final class ASTExpAddOrConcat extends ASTExpression { if (leftModel instanceof TemplateHashModelEx && rightModel instanceof TemplateHashModelEx) { TemplateHashModelEx leftModelEx = (TemplateHashModelEx) leftModel; TemplateHashModelEx rightModelEx = (TemplateHashModelEx) rightModel; - if (leftModelEx.size() == 0) { + if (leftModelEx.getHashSize() == 0) { return rightModelEx; - } else if (rightModelEx.size() == 0) { + } else if (rightModelEx.getHashSize() == 0) { return leftModelEx; } else { return new ConcatenatedHashEx(leftModelEx, rightModelEx); @@ -179,9 +178,7 @@ final class ASTExpAddOrConcat extends ASTExpression { return ParameterRole.forBinaryOperatorOperand(idx); } - private static final class ConcatenatedSequence - implements - TemplateSequenceModel { + private static final class ConcatenatedSequence implements TemplateSequenceModel { private final TemplateSequenceModel left; private final TemplateSequenceModel right; @@ -191,21 +188,63 @@ final class ASTExpAddOrConcat extends ASTExpression { } @Override - public int size() + public int getCollectionSize() throws TemplateException { - return left.size() + right.size(); + return left.getCollectionSize() + right.getCollectionSize(); } @Override - public TemplateModel get(int i) - throws TemplateException { - int ls = left.size(); - return i < ls ? left.get(i) : right.get(i - ls); + public boolean isEmptyCollection() throws TemplateException { + return left.isEmptyCollection() && right.isEmptyCollection(); + } + + @Override + public TemplateModel get(int i) throws TemplateException { + int leftSize = left.getCollectionSize(); + return i < leftSize ? left.get(i) : right.get(i - leftSize); + } + + @Override + public TemplateModelIterator iterator() throws TemplateException { + return new ConcatenatedTemplateModelIterator(left.iterator(), right.iterator()); } } - private static class ConcatenatedHash - implements TemplateHashModel { + private static class ConcatenatedTemplateModelIterator implements TemplateModelIterator { + private final TemplateModelIterator left; + private final TemplateModelIterator right; + private boolean leftExhausted; + + ConcatenatedTemplateModelIterator(TemplateModelIterator left, TemplateModelIterator right) { + this.left = left; + this.right = right; + } + + @Override + public TemplateModel next() throws TemplateException { + if (!leftExhausted) { + if (left.hasNext()) { + return left.next(); + } + leftExhausted = true; + } + return right.next(); + } + + @Override + public boolean hasNext() throws TemplateException { + if (!leftExhausted) { + if (left.hasNext()) { + return true; + } + leftExhausted = true; + } + // At this point leftExhausted is true. + return right.hasNext(); + } + } + + private static class ConcatenatedHash implements TemplateHashModel { protected final TemplateHashModel left; protected final TemplateHashModel right; @@ -222,17 +261,16 @@ final class ASTExpAddOrConcat extends ASTExpression { } @Override - public boolean isEmpty() + public boolean isEmptyHash() throws TemplateException { - return left.isEmpty() && right.isEmpty(); + return left.isEmptyHash() && right.isEmptyHash(); } } - private static final class ConcatenatedHashEx - extends ConcatenatedHash + private static final class ConcatenatedHashEx extends ConcatenatedHash implements TemplateHashModelEx { - private CollectionAndSequence keys; - private CollectionAndSequence values; + private TemplateSequenceModel keys; + private TemplateSequenceModel values; private int size; ConcatenatedHashEx(TemplateHashModelEx left, TemplateHashModelEx right) { @@ -240,34 +278,33 @@ final class ASTExpAddOrConcat extends ASTExpression { } @Override - public int size() throws TemplateException { + public int getHashSize() throws TemplateException { initKeys(); return size; } @Override - public TemplateCollectionModel keys() + public TemplateIterableModel keys() throws TemplateException { initKeys(); return keys; } @Override - public TemplateCollectionModel values() + public TemplateIterableModel values() throws TemplateException { initValues(); return values; } - private void initKeys() - throws TemplateException { + private void initKeys() throws TemplateException { if (keys == null) { HashSet keySet = new HashSet(); NativeSequence keySeq = new NativeSequence(32); addKeys(keySet, keySeq, (TemplateHashModelEx) left); addKeys(keySet, keySeq, (TemplateHashModelEx) right); size = keySet.size(); - keys = new CollectionAndSequence(keySeq); + keys = keySeq; } } @@ -277,24 +314,23 @@ final class ASTExpAddOrConcat extends ASTExpression { while (it.hasNext()) { TemplateStringModel tsm = (TemplateStringModel) it.next(); if (set.add(tsm.getAsString())) { - // The first occurence of the key decides the index; - // this is consisten with stuff like java.util.LinkedHashSet. + // The first occurrence of the key decides the index; + // this is consistent with stuff like java.util.LinkedHashSet. keySeq.add(tsm); } } } - private void initValues() - throws TemplateException { + private void initValues() throws TemplateException { if (values == null) { - NativeSequence seq = new NativeSequence(size()); - // Note: size() invokes initKeys() if needed. + NativeSequence seq = new NativeSequence(getHashSize()); + // Note: getCollectionSize() invokes initKeys() if needed. - int ln = keys.size(); + int ln = keys.getCollectionSize(); for (int i = 0; i < ln; i++) { seq.add(get(((TemplateStringModel) keys.get(i)).getAsString())); } - values = new CollectionAndSequence(seq); + values = seq; } } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 8e542ba..b0aa3b0 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 @@ -121,8 +121,8 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { putBI("int", new intBI()); putBI("interpret", new BuiltInsForStringsMisc.interpretBI()); putBI("isBoolean", new BuiltInsForMultipleTypes.is_booleanBI()); + putBI("isIterable", new BuiltInsForMultipleTypes.is_iterableBI()); putBI("isCollection", new BuiltInsForMultipleTypes.is_collectionBI()); - putBI("isCollectionEx", new BuiltInsForMultipleTypes.is_collection_exBI()); is_dateLikeBI bi = new BuiltInsForMultipleTypes.is_dateLikeBI(); putBI("isDate", bi); // misnomer putBI("isDateLike", bi); @@ -133,11 +133,9 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { putBI("isUnknownDateLike", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.UNKNOWN)); putBI("isDatetime", new BuiltInsForMultipleTypes.is_dateOfTypeBI(TemplateDateModel.DATE_TIME)); putBI("isDirective", new BuiltInsForMultipleTypes.is_directiveBI()); - putBI("isEnumerable", new BuiltInsForMultipleTypes.is_enumerableBI()); putBI("isHashEx", new BuiltInsForMultipleTypes.is_hash_exBI()); putBI("isHash", new BuiltInsForMultipleTypes.is_hashBI()); putBI("isInfinite", new is_infiniteBI()); - putBI("isIndexable", new BuiltInsForMultipleTypes.is_indexableBI()); putBI("isMarkupOutput", new BuiltInsForMultipleTypes.is_markup_outputBI()); putBI("isFunction", new BuiltInsForMultipleTypes.is_functionBI()); putBI("isNan", new is_nanBI()); @@ -250,8 +248,8 @@ abstract class ASTExpBuiltIn extends ASTExpression implements Cloneable { putBI("removeBeginning", new BuiltInsForStringsBasic.remove_beginningBI()); putBI("rtf", new BuiltInsForStringsEncoding.rtfBI()); putBI("seqContains", new seq_containsBI()); - putBI("seqIndexOf", new seq_index_ofBI(1)); - putBI("seqLastIndexOf", new seq_index_ofBI(-1)); + putBI("seqIndexOf", new seq_index_ofBI(true)); + putBI("seqLastIndexOf", new seq_index_ofBI(false)); putBI("short", new shortBI()); putBI("size", new BuiltInsForMultipleTypes.sizeBI()); putBI("sortBy", new sort_byBI()); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java index 6ca8b9e..4570948 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpBuiltInVariable.java @@ -239,7 +239,7 @@ final class ASTExpBuiltInVariable extends ASTExpression { } @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return false; } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 6e892c9..07e12fb 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 @@ -20,49 +20,75 @@ 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.TemplateIterableModel; import org.apache.freemarker.core.model.TemplateModel; -import org.apache.freemarker.core.model.TemplateStringModel; +import org.apache.freemarker.core.model.TemplateModelIterator; import org.apache.freemarker.core.model.TemplateSequenceModel; +import org.apache.freemarker.core.model.TemplateStringModel; /** {@code exp!defExp}, {@code (exp)!defExp} and the same two with {@code (exp)!}. */ class ASTExpDefault extends ASTExpression { - - static private class EmptyStringAndSequence - implements TemplateStringModel, TemplateSequenceModel, TemplateHashModelEx { - @Override + + static private class EmptyStringAndSequenceAndHash implements TemplateStringModel, TemplateSequenceModel, + TemplateHashModelEx2 { + @Override public String getAsString() { - return ""; - } - @Override + return ""; + } + + @Override public TemplateModel get(int i) { - return null; - } - @Override + return null; + } + + @Override + public TemplateModelIterator iterator() { + return TemplateModelIterator.EMPTY_ITERATOR; + } + + @Override public TemplateModel get(String s) { - return null; - } - @Override - public int size() { - return 0; - } - @Override - public boolean isEmpty() { - return true; - } - @Override - public TemplateCollectionModel keys() { - return TemplateCollectionModel.EMPTY_COLLECTION; - } - @Override - public TemplateCollectionModel values() { - return TemplateCollectionModel.EMPTY_COLLECTION; - } - - } - - static final TemplateModel EMPTY_STRING_AND_SEQUENCE = new EmptyStringAndSequence(); + return null; + } + + @Override + public int getCollectionSize() { + return 0; + } + + @Override + public boolean isEmptyCollection() throws TemplateException { + return true; + } + + @Override + public int getHashSize() throws TemplateException { + return 0; + } + + @Override + public boolean isEmptyHash() { + return true; + } + + @Override + public TemplateIterableModel keys() { + return TemplateIterableModel.EMPTY_ITERABLE; + } + + @Override + public TemplateIterableModel values() { + return TemplateIterableModel.EMPTY_ITERABLE; + } + + @Override + public KeyValuePairIterator keyValuePairIterator() throws TemplateException { + return KeyValuePairIterator.EMPTY_KEY_VALUE_PAIR_ITERATOR; + } + } + + static final TemplateModel EMPTY_STRING_AND_SEQUENCE_AND_HASH = new EmptyStringAndSequenceAndHash(); private final ASTExpression lho, rho; @@ -88,7 +114,7 @@ class ASTExpDefault extends ASTExpression { } if (left != null) return left; - else if (rho == null) return EMPTY_STRING_AND_SEQUENCE; + else if (rho == null) return EMPTY_STRING_AND_SEQUENCE_AND_HASH; else return rho.eval(env); } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java index 63e2ea2..e14e009 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpDynamicKeyName.java @@ -78,14 +78,7 @@ final class ASTExpDynamicKeyName extends ASTExpression { Environment env) throws TemplateException { if (targetModel instanceof TemplateSequenceModel) { - TemplateSequenceModel tsm = (TemplateSequenceModel) targetModel; - int size; - try { - size = tsm.size(); - } catch (Exception e) { - size = Integer.MAX_VALUE; - } - return index < size ? tsm.get(index) : null; + return ((TemplateSequenceModel) targetModel).get(index); } String s; @@ -147,7 +140,7 @@ final class ASTExpDynamicKeyName extends ASTExpression { } } - final int size = range.size(); + final int size = range.getCollectionSize(); final boolean rightUnbounded = range.isRightUnbounded(); final boolean rightAdaptive = range.isRightAdaptive(); @@ -157,14 +150,14 @@ final class ASTExpDynamicKeyName extends ASTExpression { return emptyResult(targetSeq != null); } - final int firstIdx = range.getBegining(); + final int firstIdx = range.getBeginning(); if (firstIdx < 0) { throw new TemplateException(keyExpression, "Negative range start index (", Integer.valueOf(firstIdx), ") isn't allowed for a range used for slicing."); } - final int targetSize = targetStr != null ? targetStr.length() : targetSeq.size(); + final int targetSize = targetStr != null ? targetStr.length() : targetSeq.getCollectionSize(); final int step = range.getStep(); // Right-adaptive increasing ranges can start 1 after the last element of the target, because they are like @@ -221,23 +214,13 @@ final class ASTExpDynamicKeyName extends ASTExpression { // List items are already wrapped, so the wrapper will be null: return resultSeq; } else { - final int exclEndIdx; if (step < 0 && resultSize > 1) { - if (!(range.isAffactedByStringSlicingBug() && resultSize == 2)) { - throw new TemplateException(keyExpression, - "Decreasing ranges aren't allowed for slicing strings (as it would give reversed text). " - + "The index range was: first = ", Integer.valueOf(firstIdx), - ", last = ", Integer.valueOf(firstIdx + (resultSize - 1) * step)); - } else { - // Emulate the legacy bug, where "foo"[n .. n-1] gives "" instead of an error (if n >= 1). - // Fix this in FTL [2.4] - exclEndIdx = firstIdx; - } - } else { - exclEndIdx = firstIdx + resultSize; + throw new TemplateException(keyExpression, + "Decreasing ranges aren't allowed for slicing strings (as it would give reversed text). " + + "The index range was: first = ", Integer.valueOf(firstIdx), + ", last = ", Integer.valueOf(firstIdx + (resultSize - 1) * step)); } - - return new SimpleString(targetStr.substring(firstIdx, exclEndIdx)); + return new SimpleString(targetStr.substring(firstIdx, firstIdx + resultSize)); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 f188631..a6eaab4 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 @@ -24,11 +24,11 @@ import java.util.HashMap; import java.util.LinkedHashMap; import java.util.ListIterator; -import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateIterableModel; 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.CollectionAndSequence; +import org.apache.freemarker.core.model.impl.IterableAndSequence; /** * AST expression node: <tt>{ keyExp: valueExp, ... }</tt> @@ -108,7 +108,7 @@ final class ASTExpHashLiteral extends ASTExpression { private class LinkedHash implements TemplateHashModelEx2 { private HashMap<String, TemplateModel> map; - private TemplateCollectionModel keyCollection, valueCollection; // ordered lists of keys and values + private TemplateIterableModel keyCollection, valueCollection; // ordered lists of keys and values LinkedHash(Environment env) throws TemplateException { map = new LinkedHashMap<>(); @@ -123,22 +123,22 @@ final class ASTExpHashLiteral extends ASTExpression { } @Override - public int size() { + public int getHashSize() { return size; } @Override - public TemplateCollectionModel keys() { + public TemplateIterableModel keys() { if (keyCollection == null) { - keyCollection = new CollectionAndSequence(new NativeStringCollectionCollectionEx(map.keySet())); + keyCollection = new IterableAndSequence(new NativeStringCollectionCollection(map.keySet())); } return keyCollection; } @Override - public TemplateCollectionModel values() { + public TemplateIterableModel values() { if (valueCollection == null) { - valueCollection = new CollectionAndSequence(new NativeCollectionEx(map.values())); + valueCollection = new IterableAndSequence(new NativeCollection(map.values())); } return valueCollection; } @@ -149,7 +149,7 @@ final class ASTExpHashLiteral extends ASTExpression { } @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return size == 0; } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java index 91d101a..1d3f8d9 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpListLiteral.java @@ -21,11 +21,8 @@ package org.apache.freemarker.core; import java.io.IOException; import java.util.ArrayList; -import java.util.Collections; -import java.util.List; import java.util.ListIterator; -import org.apache.freemarker.core.model.TemplateFunctionModel; import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateSequenceModel; @@ -34,9 +31,9 @@ import org.apache.freemarker.core.model.TemplateSequenceModel; */ final class ASTExpListLiteral extends ASTExpression { - final ArrayList/*<ASTExpression>*/ items; + final ArrayList<ASTExpression> items; - ASTExpListLiteral(ArrayList items) { + ASTExpListLiteral(ArrayList<ASTExpression> items) { this.items = items; items.trimToSize(); } @@ -53,30 +50,6 @@ final class ASTExpListLiteral extends ASTExpression { return list; } - /** - * For {@link TemplateFunctionModel} calls, returns the list of arguments as {@link TemplateModel}-s. - */ - // TODO [FM3][CF] This will be removed - List<TemplateModel> getModelList(Environment env) throws TemplateException { - int size = items.size(); - switch(size) { - case 0: { - return Collections.emptyList(); - } - case 1: { - return Collections.singletonList(((ASTExpression) items.get(0)).eval(env)); - } - default: { - List result = new ArrayList(items.size()); - for (ListIterator iterator = items.listIterator(); iterator.hasNext(); ) { - ASTExpression exp = (ASTExpression) iterator.next(); - result.add(exp.eval(env)); - } - return result; - } - } - } - public int size() { return items.size(); } @@ -86,7 +59,7 @@ final class ASTExpListLiteral extends ASTExpression { StringBuilder buf = new StringBuilder("["); int size = items.size(); for (int i = 0; i < size; i++) { - ASTExpression value = (ASTExpression) items.get(i); + ASTExpression value = items.get(i); buf.append(value.getCanonicalForm()); if (i != size - 1) { buf.append(", "); @@ -107,7 +80,7 @@ final class ASTExpListLiteral extends ASTExpression { return true; } for (int i = 0; i < items.size(); i++) { - ASTExpression exp = (ASTExpression) items.get(i); + ASTExpression exp = items.get(i); if (!exp.isLiteral()) { return false; } @@ -118,7 +91,7 @@ final class ASTExpListLiteral extends ASTExpression { // A hacky routine used by ASTDirVisit and ASTDirRecurse TemplateSequenceModel evaluateStringsToNamespaces(Environment env) throws TemplateException { TemplateSequenceModel val = (TemplateSequenceModel) eval(env); - NativeSequence result = new NativeSequence(val.size()); + NativeSequence result = new NativeSequence(val.getCollectionSize()); for (int i = 0; i < items.size(); i++) { Object itemExpr = items.get(i); if (itemExpr instanceof ASTExpStringLiteral) { @@ -138,12 +111,13 @@ final class ASTExpListLiteral extends ASTExpression { return result; } + @SuppressWarnings("unchecked") @Override protected ASTExpression deepCloneWithIdentifierReplaced_inner( String replacedIdentifier, ASTExpression replacement, ReplacemenetState replacementState) { - ArrayList clonedValues = (ArrayList) items.clone(); - for (ListIterator iter = clonedValues.listIterator(); iter.hasNext(); ) { - iter.set(((ASTExpression) iter.next()).deepCloneWithIdentifierReplaced( + ArrayList<ASTExpression> clonedValues = (ArrayList<ASTExpression>) items.clone(); + for (ListIterator<ASTExpression> iter = clonedValues.listIterator(); iter.hasNext(); ) { + iter.set(iter.next().deepCloneWithIdentifierReplaced( replacedIdentifier, replacement, replacementState)); } return new ASTExpListLiteral(clonedValues); http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java index 7b86e3e..edd80a5 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpRange.java @@ -55,7 +55,7 @@ final class ASTExpRange extends ASTExpression { begin, endType != END_SIZE_LIMITED ? lhoValue : begin + lhoValue, endType == END_INCLUSIVE, endType == END_SIZE_LIMITED); } else { - return new ListableRightUnboundedRangeModel(begin); + return new RightUnboundedRangeModel(begin); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java index 6b6d5e2..69bb34c 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/ASTExpression.java @@ -23,11 +23,11 @@ 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.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.TemplateStringModel; -import org.apache.freemarker.core.model.TemplateSequenceModel; import org.apache.freemarker.core.model.impl.BeanModel; /** @@ -75,7 +75,7 @@ abstract class ASTExpression extends ASTNode { } /** - * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection. + * @param seqTip Tip to display if the value type is not coercable, but it's iterable. */ String evalAndCoerceToPlainText(Environment env, String seqTip) throws TemplateException { return _EvalUtils.coerceModelToPlainText(eval(env), this, seqTip, env); @@ -86,7 +86,7 @@ abstract class ASTExpression extends ASTNode { } /** - * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection. + * @param seqTip Tip to display if the value type is not coercable, but it's iterable. */ Object evalAndCoerceToStringOrMarkup(Environment env, String seqTip) throws TemplateException { return _EvalUtils.coerceModelToStringOrMarkup(eval(env), this, seqTip, env); @@ -97,7 +97,7 @@ abstract class ASTExpression extends ASTNode { } /** - * @param seqTip Tip to display if the value type is not coercable, but it's sequence or collection. + * @param seqTip Tip to display if the value type is not coercable, but it's iterable. */ String evalAndCoerceToStringOrUnsupportedMarkup(Environment env, String seqTip) throws TemplateException { return _EvalUtils.coerceModelToStringOrUnsupportedMarkup(eval(env), this, seqTip, env); @@ -176,9 +176,9 @@ abstract class ASTExpression extends ASTNode { static boolean isEmpty(TemplateModel model) throws TemplateException { if (model instanceof BeanModel) { - return ((BeanModel) model).isEmpty(); - } else if (model instanceof TemplateSequenceModel) { - return ((TemplateSequenceModel) model).size() == 0; + return ((BeanModel) model).isEmptyHash(); + } else if (model instanceof TemplateCollectionModel) { + return ((TemplateCollectionModel) model).isEmptyCollection(); } else if (model instanceof TemplateStringModel) { String s = ((TemplateStringModel) model).getAsString(); return (s == null || s.length() == 0); @@ -187,10 +187,10 @@ abstract class ASTExpression extends ASTNode { } else if (model instanceof TemplateMarkupOutputModel) { // Note: happens just after FTL string check TemplateMarkupOutputModel mo = (TemplateMarkupOutputModel) model; return mo.getOutputFormat().isEmpty(mo); - } else if (model instanceof TemplateCollectionModel) { - return !((TemplateCollectionModel) model).iterator().hasNext(); + } else if (model instanceof TemplateIterableModel) { + return !((TemplateIterableModel) model).iterator().hasNext(); } else if (model instanceof TemplateHashModel) { - return ((TemplateHashModel) model).isEmpty(); + return ((TemplateHashModel) model).isEmptyHash(); } else if (model instanceof TemplateNumberModel || model instanceof TemplateDateModel || model instanceof TemplateBooleanModel) { http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java index 05efa98..9988200 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BoundedRangeModel.java @@ -20,6 +20,9 @@ package org.apache.freemarker.core; +import org.apache.freemarker.core.model.TemplateModel; +import org.apache.freemarker.core.model.TemplateModelIterator; + /** * A range between two integers (maybe 0 long). */ @@ -27,8 +30,7 @@ final class BoundedRangeModel extends RangeModel { private final int step, size; private final boolean rightAdaptive; - private final boolean affectedByStringSlicingBug; - + /** * @param inclusiveEnd Tells if the {@code end} index is part of the range. * @param rightAdaptive Tells if the right end of the range adapts to the size of the sliced value, if otherwise @@ -39,14 +41,35 @@ final class BoundedRangeModel extends RangeModel { step = begin <= end ? 1 : -1; size = Math.abs(end - begin) + (inclusiveEnd ? 1 : 0); this.rightAdaptive = rightAdaptive; - affectedByStringSlicingBug = inclusiveEnd; } @Override - public int size() { + public int getCollectionSize() { return size; } - + + @Override + public boolean isEmptyCollection() throws TemplateException { + return size == 0; + } + + @Override + public TemplateModelIterator iterator() throws TemplateException { + return new TemplateModelIterator() { + private long nextIndex; + + @Override + public TemplateModel next() throws TemplateException { + return uncheckedGet(nextIndex++); + } + + @Override + public boolean hasNext() throws TemplateException { + return nextIndex < getCollectionSize(); + } + }; + } + @Override int getStep() { return step; @@ -62,9 +85,4 @@ final class BoundedRangeModel extends RangeModel { return rightAdaptive; } - @Override - boolean isAffactedByStringSlicingBug() { - return affectedByStringSlicingBug; - } - } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java new file mode 100644 index 0000000..15556f8 --- /dev/null +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInForIterable.java @@ -0,0 +1,43 @@ +/* + * 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 org.apache.freemarker.core.model.TemplateIterableModel; +import org.apache.freemarker.core.model.TemplateModel; + +abstract class BuiltInForIterable extends ASTExpBuiltIn { + + @Override + TemplateModel _eval(Environment env) + throws TemplateException { + TemplateModel model = target.eval(env); + if (!(model instanceof TemplateIterableModel)) { + throw MessageUtils.newUnexpectedOperandTypeException( + target, model, + MessageUtils.EXPECTED_TYPE_ITERABLE_DESC, + TemplateIterableModel.class, + null, env); + } + return calculateResult((TemplateIterableModel) model); + } + + abstract TemplateModel calculateResult(TemplateIterableModel model) throws TemplateException; + +} \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 bac6c7f..cb7cc73 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 @@ -23,6 +23,7 @@ import org.apache.freemarker.core.model.TemplateModel; import org.apache.freemarker.core.model.TemplateSequenceModel; abstract class BuiltInForSequence extends ASTExpBuiltIn { + @Override TemplateModel _eval(Environment env) throws TemplateException { @@ -32,6 +33,7 @@ abstract class BuiltInForSequence extends ASTExpBuiltIn { } return calculateResult((TemplateSequenceModel) model); } - abstract TemplateModel calculateResult(TemplateSequenceModel tsm) - throws TemplateException; + + abstract TemplateModel calculateResult(TemplateSequenceModel tsm) throws TemplateException; + } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/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 e87bd77..4d2a9d5 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,11 @@ package org.apache.freemarker.core; -import org.apache.freemarker.core.model.TemplateCollectionModel; +import org.apache.freemarker.core.model.TemplateIterableModel; 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.CollectionAndSequence; +import org.apache.freemarker.core.model.impl.IterableAndSequence; /** * A holder for builtins that operate exclusively on hash left-hand value. @@ -35,9 +35,9 @@ class BuiltInsForHashes { @Override TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env) throws TemplateException, InvalidReferenceException { - TemplateCollectionModel keys = hashExModel.keys(); + TemplateIterableModel keys = hashExModel.keys(); if (keys == null) throw newNullPropertyException("keys", hashExModel, env); - return keys instanceof TemplateSequenceModel ? keys : new CollectionAndSequence(keys); + return keys instanceof TemplateSequenceModel ? keys : new IterableAndSequence(keys); } } @@ -46,9 +46,9 @@ class BuiltInsForHashes { @Override TemplateModel calculateResult(TemplateHashModelEx hashExModel, Environment env) throws TemplateException, InvalidReferenceException { - TemplateCollectionModel values = hashExModel.values(); + TemplateIterableModel values = hashExModel.values(); if (values == null) throw newNullPropertyException("values", hashExModel, env); - return values instanceof TemplateSequenceModel ? values : new CollectionAndSequence(values); + return values instanceof TemplateSequenceModel ? values : new IterableAndSequence(values); } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java index d1f9761..9c48dc3 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForMultipleTypes.java @@ -25,8 +25,8 @@ import java.util.Date; import org.apache.freemarker.core.model.ArgumentArrayLayout; 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.TemplateCollectionModelEx; import org.apache.freemarker.core.model.TemplateDateModel; import org.apache.freemarker.core.model.TemplateDirectiveModel; import org.apache.freemarker.core.model.TemplateFunctionModel; @@ -171,7 +171,7 @@ class BuiltInsForMultipleTypes { } @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return false; } @@ -261,21 +261,21 @@ class BuiltInsForMultipleTypes { } } - static class is_collectionBI extends ASTExpBuiltIn { + static class is_iterableBI extends ASTExpBuiltIn { @Override TemplateModel _eval(Environment env) throws TemplateException { TemplateModel tm = target.eval(env); target.assertNonNull(tm, env); - return (tm instanceof TemplateCollectionModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + return (tm instanceof TemplateIterableModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; } } - static class is_collection_exBI extends ASTExpBuiltIn { + static class is_collectionBI extends ASTExpBuiltIn { @Override TemplateModel _eval(Environment env) throws TemplateException { TemplateModel tm = target.eval(env); target.assertNonNull(tm, env); - return (tm instanceof TemplateCollectionModelEx) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; + return (tm instanceof TemplateCollectionModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; } } @@ -317,16 +317,6 @@ class BuiltInsForMultipleTypes { } } - static class is_enumerableBI extends ASTExpBuiltIn { - @Override - TemplateModel _eval(Environment env) throws TemplateException { - TemplateModel tm = target.eval(env); - target.assertNonNull(tm, env); - return (tm instanceof TemplateSequenceModel || tm instanceof TemplateCollectionModel) - ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; - } - } - static class is_hash_exBI extends ASTExpBuiltIn { @Override TemplateModel _eval(Environment env) throws TemplateException { @@ -345,15 +335,6 @@ class BuiltInsForMultipleTypes { } } - static class is_indexableBI extends ASTExpBuiltIn { - @Override - TemplateModel _eval(Environment env) throws TemplateException { - TemplateModel tm = target.eval(env); - target.assertNonNull(tm, env); - return (tm instanceof TemplateSequenceModel) ? TemplateBooleanModel.TRUE : TemplateBooleanModel.FALSE; - } - } - static class is_markup_outputBI extends ASTExpBuiltIn { @Override TemplateModel _eval(Environment env) throws TemplateException { @@ -434,21 +415,15 @@ class BuiltInsForMultipleTypes { TemplateModel model = target.eval(env); final int size; - if (model instanceof TemplateSequenceModel) { - size = ((TemplateSequenceModel) model).size(); - } else if (model instanceof TemplateCollectionModelEx) { - size = ((TemplateCollectionModelEx) model).size(); + if (model instanceof TemplateCollectionModel) { + size = ((TemplateCollectionModel) model).getCollectionSize(); } else if (model instanceof TemplateHashModelEx) { - size = ((TemplateHashModelEx) model).size(); + size = ((TemplateHashModelEx) model).getHashSize(); } else { throw MessageUtils.newUnexpectedOperandTypeException( target, model, - "extended-hash or sequence or extended collection", - new Class[] { - TemplateHashModelEx.class, - TemplateSequenceModel.class, - TemplateCollectionModelEx.class - }, + "collection (like a sequence) or extended-hash", + new Class[] { TemplateCollectionModel.class, TemplateHashModelEx.class }, null, env); } @@ -557,7 +532,7 @@ class BuiltInsForMultipleTypes { } @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return false; } } @@ -608,7 +583,7 @@ class BuiltInsForMultipleTypes { } @Override - public boolean isEmpty() { + public boolean isEmptyHash() { return false; } } http://git-wip-us.apache.org/repos/asf/incubator-freemarker/blob/cae86e18/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java ---------------------------------------------------------------------- diff --git a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java index b660a40..e598ea4 100644 --- a/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java +++ b/freemarker-core/src/main/java/org/apache/freemarker/core/BuiltInsForNodes.java @@ -64,7 +64,8 @@ class BuiltInsForNodes { return this; } AncestorSequence result = new AncestorSequence(env); - for (int seqIdx = 0; seqIdx < size(); seqIdx++) { + int size = getCollectionSize(); + for (int seqIdx = 0; seqIdx < size; seqIdx++) { TemplateNodeModel tnm = (TemplateNodeModel) get(seqIdx); String nodeName = tnm.getNodeName(); String nsURI = tnm.getNodeNamespace();