Skip to content

Commit

Permalink
Add a TemplateProcessingTracer interface.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nolaviz committed Feb 3, 2023
1 parent 2713f7a commit 8756804
Show file tree
Hide file tree
Showing 3 changed files with 158 additions and 0 deletions.
15 changes: 15 additions & 0 deletions src/main/java/freemarker/core/Environment.java
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -300,6 +302,13 @@ private void clearCachedValues() {
cachedURLEscapingCharsetSet = false;
}

/**
* Sets the tracer to use for this environment.
*/
public void setTracer(TemplateProcessingTracer tracer) {
currentTracer = tracer;
}

/**
* Processes the template to which this environment belongs to.
*/
Expand All @@ -311,12 +320,14 @@ public void process() throws TemplateException, IOException {
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();
}
Expand Down Expand Up @@ -2883,6 +2894,10 @@ private void pushElement(TemplateElement element) {
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() {
Expand Down
63 changes: 63 additions & 0 deletions src/main/java/freemarker/core/TemplateProcessingTracer.java
Original file line number Diff line number Diff line change
@@ -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();

}
80 changes: 80 additions & 0 deletions src/test/java/freemarker/core/TemplateProcessingTracerTest.java
Original file line number Diff line number Diff line change
@@ -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);
}
}

}
}

0 comments on commit 8756804

Please sign in to comment.