blob: 927ea5223f84ce5485bbce9d45506586cefb317f [file] [log] [blame]
/*
* 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 freemarker.core;
import java.math.BigDecimal;
import java.util.List;
import org.junit.Test;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import freemarker.template.Configuration;
import freemarker.template.DefaultObjectWrapper;
import freemarker.template.TemplateException;
import freemarker.test.TemplateTest;
public class MapBiTest extends TemplateTest {
private static class TestParam {
private final List<?> list;
private final String result;
public TestParam(List<?> list, String result) {
this.list = list;
this.result = result;
}
}
private static final List<TestParam> TEST_PARAMS = ImmutableList.of(
new TestParam(ImmutableList.of("a", "b", "c"), "A, B, C"),
new TestParam(ImmutableList.of("a"), "A"),
new TestParam(ImmutableList.of(), "")
);
@Override
protected Configuration createConfiguration() throws Exception {
Configuration cfg = super.createConfiguration();
cfg.setNumberFormat("0.####");
cfg.setBooleanFormat("c");
DefaultObjectWrapper objectWrapper = new DefaultObjectWrapper(Configuration.VERSION_2_3_28);
objectWrapper.setForceLegacyNonListCollections(false);
cfg.setObjectWrapper(objectWrapper);
return cfg;
}
@Test
public void testFilterWithLambda() throws Exception {
for (TestParam testParam : TEST_PARAMS) {
addToDataModel("xs", testParam.list);
// Lazy:
assertOutput(
"<#list xs?map(it -> it?upperCase) as x>${x}<#sep>, </#list>",
testParam.result);
// Eager:
assertOutput(
"<#assign fxs = xs?map(it -> it?upperCase)>" +
"${fxs?join(', ')}",
testParam.result);
}
}
@Test
public void testFilterWithFunction() throws Exception {
for (TestParam testParam : TEST_PARAMS) {
addToDataModel("xs", testParam.list);
String functionDef = "<#function toUpper s><#return s?upperCase></#function>";
// Lazy:
assertOutput(
functionDef +
"<#list xs?map(toUpper) as x>${x}<#sep>, </#list>",
testParam.result);
// Eager:
assertOutput(
functionDef +
"<#assign fxs = xs?map(toUpper)>" +
"${fxs?join(', ')}",
testParam.result);
}
}
@Test
public void testFilterWithMethod() throws Exception {
for (TestParam testParam : TEST_PARAMS) {
addToDataModel("xs", testParam.list);
addToDataModel("obj", new MapperObject());
// Lazy:
assertOutput(
"<#list xs?map(obj.toUpper) as x>${x}<#sep>, </#list>",
testParam.result);
// Eager:
assertOutput(
"<#assign fxs = xs?map(obj.toUpper)>" +
"${fxs?join(', ')}",
testParam.result);
}
}
@Test
public void testWithNumberElements() throws Exception {
addToDataModel("xs", ImmutableList.of(1, 1.55, 3));
addToDataModel("obj", new MapperObject());
assertOutput(
"<#list xs?map(n -> n * 10) as x>${x}<#sep>, </#list>",
"10, 15.5, 30");
assertOutput(
"<#function tenTimes n><#return n * 10></#function>" +
"<#list xs?map(tenTimes) as x>${x}<#sep>, </#list>",
"10, 15.5, 30");
assertOutput(
"<#list xs?map(obj.tenTimes) as x>${x}<#sep>, </#list>",
"10, 15.5, 30");
}
@Test
public void testWithBeanElements() throws Exception {
addToDataModel("xs", ImmutableList.of(new User("a"), new User("b"), new User("c")));
addToDataModel("obj", new MapperObject());
assertOutput(
"<#list xs?map(user -> user.name) as x>${x}<#sep>, </#list>",
"a, b, c");
assertOutput(
"<#function extractName user><#return user.name></#function>" +
"<#list xs?map(extractName) as x>${x}<#sep>, </#list>",
"a, b, c");
assertOutput(
"<#list xs?map(obj.extractName) as x>${x}<#sep>, </#list>",
"a, b, c");
}
@Test
public void testBuiltInsThatAllowLazyEval() throws Exception {
assertOutput("" +
"<#assign s = ''>" +
"<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
"${(1..3)?map(tenTimes)?first} ${s}", "10 1;");
assertOutput("" +
"<#assign s = ''>" +
"<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
"${(1..3)?map(tenTimes)?seqContains(20)} ${s}", "true 1;2;");
assertOutput("" +
"<#assign s = ''>" +
"<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
"${(1..3)?map(tenTimes)?seqIndexOf(20)} ${s}", "1 1;2;");
assertOutput("" +
"<#assign s = ''>" +
"<#function tenTimes(x)><#assign s += '${x};'><#return x * 10></#function>" +
"${[1, 2, 3, 2, 5]?map(tenTimes)?seqLastIndexOf(20)} ${s}", "3 1;2;3;2;5;");
// For these this test can't check that there was no sequence built, but at least we know they are working:
assertOutput("${(1..3)?map(it -> it * 10)?min}", "10");
assertOutput("${(1..3)?map(it -> it * 10)?max}", "30");
assertOutput("${(1..3)?map(it -> it * 10)?join(', ')}", "10, 20, 30");
}
@Test
public void testErrorMessages() {
assertErrorContains("${1?map(it -> it)}", TemplateException.class,
"sequence or collection", "number");
assertErrorContains("${[]?map(1)}", TemplateException.class,
"method or function or lambda", "number");
assertErrorContains("<#function f></#function>${['x']?map(f)}", TemplateException.class,
"Function", "0 parameters", "1");
assertErrorContains("<#function f x y z></#function>${['x']?map(f)}", TemplateException.class,
"function", "parameter \"y\"");
assertErrorContains("<#function f x></#function>${['x']?map(f)}", TemplateException.class,
"null");
assertErrorContains("${[]?map(() -> 1)}", ParseException.class,
"lambda", "1 parameter", "declared 0");
assertErrorContains("${[]?map((i, j) -> 1)}", ParseException.class,
"lambda", "1 parameter", "declared 2");
}
@Test
public void testNonSequenceInput() throws Exception {
addToDataModel("coll", ImmutableSet.of("a", "b", "c"));
assertErrorContains("${coll?map(it -> it?upperCase)[0]}", "sequence", "evaluated to an extended_collection");
assertErrorContains("[#ftl][#assign t = coll?map(it -> it?upperCase)]",
"lazy transformation", "?sequence", "[#list");
assertOutput("${coll?sequence?map(it -> it?upperCase)[0]}", "A");
assertOutput("${coll?map(it -> it?upperCase)?sequence[0]}", "A");
assertOutput("<#list coll?map(it -> it?upperCase) as it>${it}</#list>", "ABC");
}
public static class MapperObject {
public String toUpper(String s) {
return s.toUpperCase();
}
public BigDecimal tenTimes(BigDecimal n) {
return n.movePointRight(1);
}
public String extractName(User user) { return user.getName(); }
}
public static class User {
private final String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
}
}