Merge pull request #89 from nolaviz/nolaviz-patch1
Add a simple mechanism for runtime debugging of templates.
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index bb43571..6cbfe28 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
@@ -2879,6 +2881,13 @@
};
}
+ /**
+ * 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 @@
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 53aeee5..856e5b0 100644
--- a/src/main/java/freemarker/core/ListElseContainer.java
+++ b/src/main/java/freemarker/core/ListElseContainer.java
@@ -37,10 +37,10 @@
@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 0000000..434891e
--- /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.
+ * <p>
+ * 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 0000000..f069dc1
--- /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>" +
+ "<#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>" +
+ "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(TEMPLATE_TEXT);
+ Environment env = t.createProcessingEnvironment(null, sw);
+ env.setTracer(tracer);
+ env.process();
+
+ List<String> expected = Arrays.asList("Yup.", "Always.", "${item}", "${item}", "${item}", "Yup.");
+ assertEquals(expected, tracer.elementsVisited);
+ }
+
+ private static class Tracer implements TemplateProcessingTracer {
+ final ArrayList<String> 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) {}
+ }
+}