blob: 6fc03f38dd545dafa8585361f90fdf4aa7edef7f [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.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import org.junit.Test;
import freemarker.template.Configuration;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
import freemarker.template.utility.ObjectFactory;
import freemarker.test.TemplateTest;
public class DirectiveCallPlaceTest extends TemplateTest {
@Override
protected Configuration createConfiguration() {
return new Configuration(Configuration.VERSION_2_3_22);
}
@Test
public void testCustomDataBasics() throws IOException, TemplateException {
addTemplate(
"customDataBasics.ftl",
"<@uc>Abc</@uc> <@uc>x=${x}</@uc> <@uc>Ab<#-- -->c</@uc> <@lc/><@lc></@lc> <@lc>Abc</@lc>");
CachingTextConverterDirective.resetCacheRecreationCount();
for (int i = 0; i < 3; i++) {
assertOutputForNamed(
"customDataBasics.ftl",
"ABC[cached 1] X=123 ABC[cached 2] abc[cached 3]");
}
}
@Test
public void testCustomDataProviderMismatch() throws IOException, TemplateException {
addTemplate(
"customDataProviderMismatch.ftl",
"<#list [uc, lc, uc] as d><#list 1..2 as _><@d>Abc</@d></#list></#list>");
CachingTextConverterDirective.resetCacheRecreationCount();
assertOutputForNamed(
"customDataProviderMismatch.ftl",
"ABC[cached 1]ABC[cached 1]abc[cached 2]abc[cached 2]ABC[cached 3]ABC[cached 3]");
assertOutputForNamed(
"customDataProviderMismatch.ftl",
"ABC[cached 3]ABC[cached 3]abc[cached 4]abc[cached 4]ABC[cached 5]ABC[cached 5]");
}
@Test
public void testPositions() throws IOException, TemplateException {
addTemplate(
"positions.ftl",
"<@pa />\n"
+ "..<@pa\n"
+ "/><@pa>xxx</@>\n"
+ "<@pa>{<@pa/> <@pa/>}</@>\n"
+ "${curDirLine}<@argP p=curDirLine?string>${curDirLine}</@argP>${curDirLine}\n"
+ "<#macro m p>(p=${p}){<#nested>}</#macro>\n"
+ "${curDirLine}<@m p=curDirLine?string>${curDirLine}</@m>${curDirLine}");
assertOutputForNamed(
"positions.ftl",
"[positions.ftl:1:1-1:7]"
+ "..[positions.ftl:2:3-3:2]"
+ "[positions.ftl:3:3-3:14]xxx\n"
+ "[positions.ftl:4:1-4:24]{[positions.ftl:4:7-4:12] [positions.ftl:4:14-4:19]}\n"
+ "-(p=5){-}-\n"
+ "-(p=7){-}-"
);
}
@SuppressWarnings("boxing")
@Override
protected Object createDataModel() {
Map<String, Object> dm = new HashMap<String, Object>();
dm.put("uc", new CachingUpperCaseDirective());
dm.put("lc", new CachingLowerCaseDirective());
dm.put("pa", new PositionAwareDirective());
dm.put("argP", new ArgPrinterDirective());
dm.put("curDirLine", new CurDirLineScalar());
dm.put("x", 123);
return dm;
}
private abstract static class CachingTextConverterDirective implements TemplateDirectiveModel {
/** Only needed for testing. */
private static AtomicInteger cacheRecreationCount = new AtomicInteger();
/** Only needed for testing. */
static void resetCacheRecreationCount() {
cacheRecreationCount.set(0);
}
public void execute(Environment env, Map params, TemplateModel[] loopVars, final TemplateDirectiveBody body)
throws TemplateException, IOException {
if (body == null) {
return;
}
final String convertedText;
final DirectiveCallPlace callPlace = env.getCurrentDirectiveCallPlace();
if (callPlace.isNestedOutputCacheable()) {
try {
convertedText = (String) callPlace.getOrCreateCustomData(
getTextConversionIdentity(), new ObjectFactory<String>() {
public String createObject() throws TemplateException, IOException {
return convertBodyText(body)
+ "[cached " + cacheRecreationCount.incrementAndGet() + "]";
}
});
} catch (CallPlaceCustomDataInitializationException e) {
throw new TemplateModelException("Failed to pre-render nested content", e);
}
} else {
convertedText = convertBodyText(body);
}
env.getOut().write(convertedText);
}
protected abstract Class getTextConversionIdentity();
private String convertBodyText(TemplateDirectiveBody body) throws TemplateException,
IOException {
StringWriter sw = new StringWriter();
body.render(sw);
return convertText(sw.toString());
}
protected abstract String convertText(String s);
}
private static class CachingUpperCaseDirective extends CachingTextConverterDirective {
@Override
protected String convertText(String s) {
return s.toUpperCase();
}
@Override
protected Class getTextConversionIdentity() {
return CachingUpperCaseDirective.class;
}
}
private static class CachingLowerCaseDirective extends CachingTextConverterDirective {
@Override
protected String convertText(String s) {
return s.toLowerCase();
}
@Override
protected Class getTextConversionIdentity() {
return CachingLowerCaseDirective.class;
}
}
private static class PositionAwareDirective implements TemplateDirectiveModel {
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException {
Writer out = env.getOut();
DirectiveCallPlace callPlace = env.getCurrentDirectiveCallPlace();
out.write("[");
out.write(getTemplateSourceName(callPlace));
out.write(":");
out.write(Integer.toString(callPlace.getBeginLine()));
out.write(":");
out.write(Integer.toString(callPlace.getBeginColumn()));
out.write("-");
out.write(Integer.toString(callPlace.getEndLine()));
out.write(":");
out.write(Integer.toString(callPlace.getEndColumn()));
out.write("]");
if (body != null) {
body.render(out);
}
}
private String getTemplateSourceName(DirectiveCallPlace callPlace) {
return ((UnifiedCall) callPlace).getTemplate().getSourceName();
}
}
private static class ArgPrinterDirective implements TemplateDirectiveModel {
public void execute(Environment env, Map params, TemplateModel[] loopVars, TemplateDirectiveBody body)
throws TemplateException, IOException {
final Writer out = env.getOut();
if (params.size() > 0) {
out.write("(p=");
out.write(((TemplateScalarModel) params.get("p")).getAsString());
out.write(")");
}
if (body != null) {
out.write("{");
body.render(out);
out.write("}");
}
}
}
private static class CurDirLineScalar implements TemplateScalarModel {
public String getAsString() throws TemplateModelException {
DirectiveCallPlace callPlace = Environment.getCurrentEnvironment().getCurrentDirectiveCallPlace();
return callPlace != null
? String.valueOf(Environment.getCurrentEnvironment().getCurrentDirectiveCallPlace().getBeginLine())
: "-";
}
}
}