Add a `TemplateProcessingTracer` interface.
The `TemplateProcessingTracer` receives callbacks from the `Environment`
while processing a template; it can then analyze the template behavior
at runtime, e.g. producing code-coverage maps or profiling performance.
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 0bd264b..042f42f 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -188,6 +188,8 @@
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
@@ -301,6 +303,13 @@
}
/**
+ * Sets the tracer to use for this environment.
+ */
+ public void setTracer(TemplateProcessingTracer tracer) {
+ currentTracer = tracer;
+ }
+
+ /**
* Processes the template to which this environment belongs to.
*/
public void process() throws TemplateException, IOException {
@@ -311,12 +320,14 @@
clearCachedValues();
try {
doAutoImportsAndIncludes(this);
+ if (currentTracer != null) currentTracer.start();
visit(getTemplate().getRootTreeNode());
// It's here as we must not flush if there was an exception.
if (getAutoFlush()) {
out.flush();
}
} finally {
+ if (currentTracer != null) currentTracer.end();
// It's just to allow the GC to free memory...
clearCachedValues();
}
@@ -2883,6 +2894,10 @@
this.instructionStack = instructionStack;
}
instructionStack[newSize - 1] = element;
+ if (currentTracer != null) {
+ currentTracer.trace(element.getTemplate(), element.getBeginColumn(), element.getBeginLine(),
+ element.getEndColumn(), element.getEndLine(), element.isLeaf());
+ }
}
private void popElement() {
diff --git a/src/main/java/freemarker/core/TemplateProcessingTracer.java b/src/main/java/freemarker/core/TemplateProcessingTracer.java
new file mode 100644
index 0000000..0075a76
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateProcessingTracer.java
@@ -0,0 +1,63 @@
+/*
+ * 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.
+ * <p>
+ * Use {@link Environment#setTracer(TemplateProcessingTracer)} to configure a tracer for the current environment.
+ *
+ * @since 2.3.33
+ */
+public interface TemplateProcessingTracer {
+
+ /**
+ * Invoked when {@link Template.process()} starts processing the template.
+ *
+ * @since 2.3.23
+ */
+ void start();
+
+ /**
+ * 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 trace(Template template, int beginColumn, int beginLine, int endColumn, int endLine,
+ boolean isLeafElement);
+
+ /**
+ * Invoked when template processing is finished.
+ *
+ * @since 2.3.23
+ */
+ void end();
+
+}
diff --git a/src/test/java/freemarker/core/TemplateProcessingTracerTest.java b/src/test/java/freemarker/core/TemplateProcessingTracerTest.java
new file mode 100644
index 0000000..a2ebb2f
--- /dev/null
+++ b/src/test/java/freemarker/core/TemplateProcessingTracerTest.java
@@ -0,0 +1,80 @@
+/*
+ * 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>" +
+ "<#if 1 == 1>Yup.\n</#if>" +
+ "Always.\n" +
+ "<#list [1, 2, 3] as item>\n" +
+ "${item}<#else>\n" +
+ "Nope.\n" +
+ "</#list>\n" +
+ "<#list [] as item>\n" +
+ "${item}<#else>\n" +
+ "Yup.\n" +
+ "</#list>\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();
+ Environment env = t.createProcessingEnvironment(null, sw);
+ env.setTracer(tracer);
+ env.process();
+
+ List<Integer> expected = Arrays.asList(2, 3, 5, 5, 5, 9);
+ assertEquals(expected, tracer.linesVisited);
+ }
+
+ private static class Tracer implements TemplateProcessingTracer {
+ ArrayList<Integer> linesVisited;
+
+ Tracer() {
+ linesVisited = new ArrayList<>();
+ }
+
+ public void start() {}
+ public void end() {}
+
+ public void trace(Template template, int beginColumn, int beginLine, int endColumn, int endLine,
+ boolean isLeafElement) {
+ if (isLeafElement) {
+ linesVisited.add(beginLine);
+ }
+ }
+
+ }
+}