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) {}
+    }
+}