diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java index 0bd264bb0..042f42f96 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 @@ -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. */ @@ -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(); } @@ -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() { diff --git a/src/main/java/freemarker/core/TemplateProcessingTracer.java b/src/main/java/freemarker/core/TemplateProcessingTracer.java new file mode 100644 index 000000000..0075a764f --- /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. + *
+ * 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 000000000..a2ebb2f44
--- /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