diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java index 6cbfe28bf..66f121478 100644 --- a/src/main/java/freemarker/core/Environment.java +++ b/src/main/java/freemarker/core/Environment.java @@ -188,7 +188,7 @@ public final class Environment extends Configurable { private boolean fastInvalidReferenceExceptions; - private TemplateProcessingTracer currentTracer; + private TemplateProcessingTracer templateProcessingTracer; /** * Retrieves the environment object associated with the current thread, or {@code null} if there's no template @@ -2882,10 +2882,22 @@ public TemplateModel get(String key) throws TemplateModelException { } /** - * Sets the tracer to use for this environment. + * Sets the {@link TemplateProcessingTracer} to use for this {@link Environment}; + * can be {@code null} to not have one. The default is also {@code null}. + * + * @since 2.3.33 + */ + public void setTemplateProcessingTracer(TemplateProcessingTracer templateProcessingTracer) { + this.templateProcessingTracer = templateProcessingTracer; + } + + /** + * Getter pair of {@link #setTemplateProcessingTracer(TemplateProcessingTracer)}. Can be {@code null}. + * + * @since 2.3.33 */ - public void setTracer(TemplateProcessingTracer tracer) { - currentTracer = tracer; + public TemplateProcessingTracer getTemplateProcessingTracer() { + return templateProcessingTracer; } private void pushElement(TemplateElement element) { @@ -2900,19 +2912,15 @@ private void pushElement(TemplateElement element) { this.instructionStack = instructionStack; } instructionStack[newSize - 1] = element; - if (currentTracer != null) { - currentTracer.enterElement(element.getTemplate(), - element.getBeginColumn(), element.getBeginLine(), - element.getEndColumn(), element.getEndLine(), element.isLeaf()); + if (templateProcessingTracer != null) { + templateProcessingTracer.enterElement(this, element); } } private void popElement() { - if (currentTracer != null) { + if (templateProcessingTracer != null) { TemplateElement element = instructionStack[instructionStackSize - 1]; - currentTracer.exitElement(element.getTemplate(), - element.getBeginColumn(), element.getBeginLine(), - element.getEndColumn(), element.getEndLine()); + templateProcessingTracer.exitElement(this); } instructionStackSize--; } diff --git a/src/main/java/freemarker/core/IfBlock.java b/src/main/java/freemarker/core/IfBlock.java index 223d755cb..52615d2cd 100644 --- a/src/main/java/freemarker/core/IfBlock.java +++ b/src/main/java/freemarker/core/IfBlock.java @@ -42,12 +42,24 @@ void addBlock(ConditionalBlock block) { @Override TemplateElement[] accept(Environment env) throws TemplateException, IOException { int ln = getChildCount(); - for (int i = 0; i < ln; i++) { - ConditionalBlock cblock = (ConditionalBlock) getChild(i); - Expression condition = cblock.condition; - env.replaceElementStackTop(cblock); - if (condition == null || condition.evalToBoolean(env)) { - return cblock.getChildBuffer(); + if (env.getTemplateProcessingTracer() == null) { + for (int i = 0; i < ln; i++) { + ConditionalBlock cblock = (ConditionalBlock) getChild(i); + Expression condition = cblock.condition; + env.replaceElementStackTop(cblock); + if (condition == null || condition.evalToBoolean(env)) { + return cblock.getChildBuffer(); + } + } + } else { + for (int i = 0; i < ln; i++) { + ConditionalBlock cblock = (ConditionalBlock) getChild(i); + Expression condition = cblock.condition; + env.replaceElementStackTop(cblock); + if (condition == null || condition.evalToBoolean(env)) { + env.visit(cblock); + return null; + } } } return null; diff --git a/src/main/java/freemarker/core/ListElseContainer.java b/src/main/java/freemarker/core/ListElseContainer.java index 856e5b02b..4e307c376 100644 --- a/src/main/java/freemarker/core/ListElseContainer.java +++ b/src/main/java/freemarker/core/ListElseContainer.java @@ -37,9 +37,24 @@ public ListElseContainer(IteratorBlock listPart, ElseOfList elsePart) { @Override TemplateElement[] accept(Environment env) throws TemplateException, IOException { - if (listPart.acceptWithResult(env)) { + boolean hadItems; + + TemplateProcessingTracer templateProcessingTracer = env.getTemplateProcessingTracer(); + if (templateProcessingTracer == null) { + hadItems = listPart.acceptWithResult(env); + } else { + templateProcessingTracer.enterElement(env, listPart); + try { + hadItems = listPart.acceptWithResult(env); + } finally { + templateProcessingTracer.exitElement(env); + } + } + + if (hadItems) { return null; } + return new TemplateElement[] { elsePart }; } diff --git a/src/main/java/freemarker/core/TemplateElement.java b/src/main/java/freemarker/core/TemplateElement.java index 6cb9b5411..2e79d9345 100644 --- a/src/main/java/freemarker/core/TemplateElement.java +++ b/src/main/java/freemarker/core/TemplateElement.java @@ -37,7 +37,7 @@ * it. */ @Deprecated -abstract public class TemplateElement extends TemplateObject { +abstract public class TemplateElement extends TemplateObject implements TemplateProcessingTracer.TracedElement { private static final int INITIAL_REGULATED_CHILD_BUFFER_CAPACITY = 6; @@ -89,9 +89,9 @@ abstract public class TemplateElement extends TemplateObject { * One-line description of the element, that contains all the information that is used in * {@link #getCanonicalForm()}, except the nested content (elements) of the element. The expressions inside the * element (the parameters) has to be shown. Meant to be used for stack traces, also for tree views that don't go - * down to the expression-level. There are no backward-compatibility guarantees regarding the format used ATM, but - * it must be regular enough to be machine-parseable, and it must contain all information necessary for restoring an - * AST equivalent to the original. + * down to the expression-level. There are no backward-compatibility guarantees regarding the format used, although + * it shouldn't change unless to fix a bug. It must be regular enough to be machine-parseable, and it must contain + * all information necessary for restoring an AST equivalent to the original. * * This final implementation calls {@link #dump(boolean) dump(false)}. * diff --git a/src/main/java/freemarker/core/TemplateObject.java b/src/main/java/freemarker/core/TemplateObject.java index e535d0839..7ef6cfb46 100644 --- a/src/main/java/freemarker/core/TemplateObject.java +++ b/src/main/java/freemarker/core/TemplateObject.java @@ -86,23 +86,35 @@ void setLocation(Template template, int beginColumn, int beginLine, int endColum this.endColumn = endColumn; this.endLine = endLine; } - - public final int getBeginColumn() { - return beginColumn; - } + /** + * 1-based index of the line (row) of the first character of the element in the template. + */ public final int getBeginLine() { return beginLine; } - public final int getEndColumn() { - return endColumn; + /** + * 1-based index of the column of the first character of the element in the template. + */ + public final int getBeginColumn() { + return beginColumn; } + /** + * 1-based index of the line (row) of the last character of the element in the template. + */ public final int getEndLine() { return endLine; } + /** + * 1-based index of the column of the last character of the element in the template. + */ + public final int getEndColumn() { + return endColumn; + } + /** * Returns a string that indicates * where in the template source, this object is. diff --git a/src/main/java/freemarker/core/TemplateProcessingTracer.java b/src/main/java/freemarker/core/TemplateProcessingTracer.java index 434891e24..2130e6c47 100644 --- a/src/main/java/freemarker/core/TemplateProcessingTracer.java +++ b/src/main/java/freemarker/core/TemplateProcessingTracer.java @@ -19,38 +19,77 @@ 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, + * Hooks to monitor as templates run. This may be used to implement profiling, coverage analysis, execution tracing, * and other on-the-fly debugging mechanisms. *
- * Use {@link Environment#setTracer(TemplateProcessingTracer)} to configure a tracer for the current environment.
+ * Use {@link Environment#setTemplateProcessingTracer(TemplateProcessingTracer)} to set 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.
+ * Invoked by {@link Environment} whenever it starts processing a new template element. A template element is a
+ * directive call, an interpolation (like ${...}
), a comment block, or static text. Expressions
+ * are not template elements.
*
* @since 2.3.23
*/
- void enterElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine,
- boolean isLeafElement);
+ void enterElement(Environment env, TracedElement tracedElement);
/**
* Invoked by {@link Environment} whenever it completes processing a new template element.
+ *
+ * @see #enterElement(Environment, TracedElement)
*
* @since 2.3.23
*/
- void exitElement(Template template, int beginColumn, int beginLine, int endColumn, int endLine);
+ void exitElement(Environment env);
+
+ /**
+ * Information about the template element that we enter of exit.
+ */
+ interface TracedElement {
+ /**
+ * The {@link Template} that contains this element.
+ */
+ Template getTemplate();
+
+ /**
+ * 1-based index of the line (row) of the first character of the element in the template.
+ */
+ int getBeginLine();
+
+ /**
+ * 1-based index of the column of the first character of the element in the template.
+ */
+ int getBeginColumn();
+
+ /**
+ * 1-based index of the line (row) of the last character of the element in the template.
+ */
+ int getEndColumn();
+
+ /**
+ * 1-based index of the column of the last character of the element in the template.
+ */
+ int getEndLine();
+
+ /**
+ * If this is an element that has no nested elements.
+ */
+ boolean isLeaf();
+
+ /**
+ * One-line description of the element, that also contains the parameter expressions, but not the nested content
+ * (child elements). There are no hard backward-compatibility guarantees regarding the format used, although
+ * it shouldn't change unless to fix a bug.
+ */
+ String getDescription();
+ }
}
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index 967d222f5..c6fcc31fd 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -764,7 +764,8 @@ public void addImport(LibraryLoad ll) {
*
* @param beginColumn the first column of the requested source, 1-based
* @param beginLine the first line of the requested source, 1-based
- * @param endColumn the last column of the requested source, 1-based
+ * @param endColumn the last column of the requested source, 1-based. If this is beyond the last character of the
+ * line, it assumes that you want to whole line.
* @param endLine the last line of the requested source, 1-based
*
* @see freemarker.core.TemplateObject#getSource()
@@ -787,7 +788,7 @@ public String getSource(int beginColumn,
}
}
int lastLineLength = lines.get(endLine).toString().length();
- int trailingCharsToDelete = lastLineLength - endColumn - 1;
+ int trailingCharsToDelete = endColumn < lastLineLength ? lastLineLength - endColumn - 1 : 0;
buf.delete(0, beginColumn);
buf.delete(buf.length() - trailingCharsToDelete, buf.length());
return buf.toString();
diff --git a/src/manual/en_US/book.xml b/src/manual/en_US/book.xml
index 26bc653dc..d5969fe66 100644
--- a/src/manual/en_US/book.xml
+++ b/src/manual/en_US/book.xml
@@ -30140,6 +30140,21 @@ TemplateModel x = env.getVariable("x"); // get variable x
+
+