diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java index bb435714f..6cbfe28bf 100644 --- a/src/main/java/freemarker/core/Environment.java +++ b/src/main/java/freemarker/core/Environment.java @@ -188,6 +188,8 @@ public final class Environment extends Configurable { private boolean fastInvalidReferenceExceptions; + private TemplateProcessingTracer currentTracer; + /** * Retrieves the environment object associated with the current thread, or {@code null} if there's no template * processing going on in this thread. Data model implementations that need access to the environment can call this @@ -2879,6 +2881,13 @@ public TemplateModel get(String key) throws TemplateModelException { }; } + /** + * Sets the tracer to use for this environment. + */ + public void setTracer(TemplateProcessingTracer tracer) { + currentTracer = tracer; + } + private void pushElement(TemplateElement element) { final int newSize = ++instructionStackSize; TemplateElement[] instructionStack = this.instructionStack; @@ -2891,9 +2900,20 @@ private void pushElement(TemplateElement element) { this.instructionStack = instructionStack; } instructionStack[newSize - 1] = element; + if (currentTracer != null) { + currentTracer.enterElement(element.getTemplate(), + element.getBeginColumn(), element.getBeginLine(), + element.getEndColumn(), element.getEndLine(), element.isLeaf()); + } } private void popElement() { + if (currentTracer != null) { + TemplateElement element = instructionStack[instructionStackSize - 1]; + currentTracer.exitElement(element.getTemplate(), + element.getBeginColumn(), element.getBeginLine(), + element.getEndColumn(), element.getEndLine()); + } instructionStackSize--; } diff --git a/src/main/java/freemarker/core/ListElseContainer.java b/src/main/java/freemarker/core/ListElseContainer.java index 53aeee5fa..856e5b02b 100644 --- a/src/main/java/freemarker/core/ListElseContainer.java +++ b/src/main/java/freemarker/core/ListElseContainer.java @@ -37,10 +37,10 @@ public ListElseContainer(IteratorBlock listPart, ElseOfList elsePart) { @Override TemplateElement[] accept(Environment env) throws TemplateException, IOException { - if (!listPart.acceptWithResult(env)) { - return elsePart.accept(env); + if (listPart.acceptWithResult(env)) { + return null; } - return null; + return new TemplateElement[] { elsePart }; } @Override diff --git a/src/main/java/freemarker/core/TemplateProcessingTracer.java b/src/main/java/freemarker/core/TemplateProcessingTracer.java new file mode 100644 index 000000000..434891e24 --- /dev/null +++ b/src/main/java/freemarker/core/TemplateProcessingTracer.java @@ -0,0 +1,56 @@ +/* + * 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 freemarker.ext.util.IdentityHashMap; +import freemarker.template.Configuration; +import freemarker.template.Template; +import freemarker.template.TemplateDirectiveModel; +import freemarker.template.TemplateTransformModel; +import freemarker.template.utility.ObjectFactory; + +/** + * Run-time tracer plug-in. This may be * used to implement profiling, coverage analytis, execution tracing, + * and other on-the-fly debugging mechanisms. + *

+ * Use {@link Environment#setTracer(TemplateProcessingTracer)} to configure a tracer for the current environment. + * + * @since 2.3.33 + */ +public interface TemplateProcessingTracer { + + /** + * Invoked by {@link Environment} whenever it starts processing a new template element. {@code + * isLeafElement} indicates whether this element is a leaf, or whether the tracer should expect + * to receive lower-level elements within the context of this one. + * + * @since 2.3.23 + */ + void enterElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine, + boolean isLeafElement); + + /** + * Invoked by {@link Environment} whenever it completes processing a new template element. + * + * @since 2.3.23 + */ + void exitElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine); + +} diff --git a/src/test/java/freemarker/core/TemplateProcessingTracerTest.java b/src/test/java/freemarker/core/TemplateProcessingTracerTest.java new file mode 100644 index 000000000..f069dc1b8 --- /dev/null +++ b/src/test/java/freemarker/core/TemplateProcessingTracerTest.java @@ -0,0 +1,83 @@ +/* + * 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 static org.junit.Assert.*; + +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.junit.Test; + +import freemarker.template.Configuration; +import freemarker.template.Template; + +public class TemplateProcessingTracerTest { + + private static final String TEMPLATE_TEXT = + "<#if 0 == 1>Nope.\n" + + "<#if 1 == 1>Yup.\n" + + "Always.\n" + + "<#list [1, 2, 3] as item>\n" + + "${item}<#else>\n" + + "Nope.\n" + + "\n" + + "<#list [] as item>\n" + + "${item}<#else>" + + "Yup.\n" + + "\n"; + + @Test + public void test() throws Exception { + Configuration cfg = new Configuration(Configuration.VERSION_2_3_32); + Template t = new Template("test.ftl", TEMPLATE_TEXT, cfg); + StringWriter sw = new StringWriter(); + Tracer tracer = new Tracer(TEMPLATE_TEXT); + Environment env = t.createProcessingEnvironment(null, sw); + env.setTracer(tracer); + env.process(); + + List expected = Arrays.asList("Yup.", "Always.", "${item}", "${item}", "${item}", "Yup."); + assertEquals(expected, tracer.elementsVisited); + } + + private static class Tracer implements TemplateProcessingTracer { + final ArrayList elementsVisited; + final String[] templateLines; + + Tracer(String template) { + elementsVisited = new ArrayList<>(); + templateLines = template.split("\\n"); + } + + public void enterElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine, + boolean isLeafElement) { + if (isLeafElement) { + String line = templateLines[beginLine - 1]; + String elementText = line.substring(beginColumn - 1, + endLine == beginLine ? Math.min(endColumn, line.length()) : line.length()); + elementsVisited.add(elementText); + } + } + + public void exitElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine) {} + } +}