Merge remote-tracking branch 'origin/2.3-gae'
Conflicts:
src/main/java/freemarker/core/DebugBreak.java
src/main/java/freemarker/core/Environment.java
src/main/java/freemarker/core/Expression.java
src/main/java/freemarker/core/IfBlock.java
src/main/java/freemarker/core/Macro.java
src/main/java/freemarker/core/TemplateObject.java
src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
src/main/java/freemarker/core/UnifiedCall.java
src/main/javacc/FTL.jj
diff --git a/src/main/java/freemarker/cache/TemplateLookupStrategy.java b/src/main/java/freemarker/cache/TemplateLookupStrategy.java
index 0ce7c67..6b50aa9 100644
--- a/src/main/java/freemarker/cache/TemplateLookupStrategy.java
+++ b/src/main/java/freemarker/cache/TemplateLookupStrategy.java
@@ -26,7 +26,7 @@
import freemarker.template.Template;
/**
- * Finds the {@link TemplateLoader}-level (storage-level) template source for the template name witch which the template
+ * Finds the {@link TemplateLoader}-level (storage-level) template source for the template name with which the template
* was requested (as in {@link Configuration#getTemplate(String)}). This usually means trying various
* {@link TemplateLoader}-level template names (so called source names; see also {@link Template#getSourceName()}) that
* were deduced from the requested name. Trying a name usually means calling
diff --git a/src/main/java/freemarker/core/Assignment.java b/src/main/java/freemarker/core/Assignment.java
index 0521250..bdce203 100644
--- a/src/main/java/freemarker/core/Assignment.java
+++ b/src/main/java/freemarker/core/Assignment.java
@@ -101,7 +101,7 @@
}
@Override
- void accept(Environment env) throws TemplateException {
+ TemplateElement[] accept(Environment env) throws TemplateException {
final Environment.Namespace namespace;
if (namespaceExp == null) {
switch (scope) {
@@ -193,6 +193,7 @@
} else {
namespace.put(variableName, value);
}
+ return null;
}
@Override
diff --git a/src/main/java/freemarker/core/AssignmentInstruction.java b/src/main/java/freemarker/core/AssignmentInstruction.java
index d575ca2..87a7d56 100644
--- a/src/main/java/freemarker/core/AssignmentInstruction.java
+++ b/src/main/java/freemarker/core/AssignmentInstruction.java
@@ -35,28 +35,24 @@
AssignmentInstruction(int scope) {
this.scope = scope;
- setRegulatedChildBufferCapacity(1);
+ setChildBufferCapacity(1);
}
void addAssignment(Assignment assignment) {
- addRegulatedChild(assignment);
+ addChild(assignment);
}
void setNamespaceExp(Expression namespaceExp) {
this.namespaceExp = namespaceExp;
- int ln = getRegulatedChildCount();
+ int ln = getChildCount();
for (int i = 0; i < ln; i++) {
- ((Assignment) getRegulatedChild(i)).setNamespaceExp(namespaceExp);
+ ((Assignment) getChild(i)).setNamespaceExp(namespaceExp);
}
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- int ln = getRegulatedChildCount();
- for (int i = 0; i < ln; i++) {
- Assignment assignment = (Assignment) getRegulatedChild(i);
- env.visit(assignment);
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
@Override
@@ -66,12 +62,12 @@
buf.append(Assignment.getDirectiveName(scope));
if (canonical) {
buf.append(' ');
- int ln = getRegulatedChildCount();
+ int ln = getChildCount();
for (int i = 0; i < ln; i++) {
if (i != 0) {
buf.append(", ");
}
- Assignment assignment = (Assignment) getRegulatedChild(i);
+ Assignment assignment = (Assignment) getChild(i);
buf.append(assignment.getCanonicalForm());
}
} else {
@@ -114,12 +110,6 @@
}
@Override
- public TemplateElement postParseCleanup(boolean stripWhitespace) throws ParseException {
- super.postParseCleanup(stripWhitespace);
- return this;
- }
-
- @Override
boolean isNestedBlockRepeater() {
return false;
}
diff --git a/src/main/java/freemarker/core/AttemptBlock.java b/src/main/java/freemarker/core/AttemptBlock.java
index be82cf2..5222500 100644
--- a/src/main/java/freemarker/core/AttemptBlock.java
+++ b/src/main/java/freemarker/core/AttemptBlock.java
@@ -24,24 +24,26 @@
import freemarker.template.TemplateException;
/**
- * #attempt element; might has a nested {@link RecoveryBlock}.
+ * Holder for the attempted section of the #attempt element and of the nested #recover element ({@link RecoveryBlock}).
*/
final class AttemptBlock extends TemplateElement {
- private TemplateElement attemptBlock;
- private RecoveryBlock recoveryBlock;
+ private TemplateElement attemptedSection;
+ private RecoveryBlock recoverySection;
- AttemptBlock(TemplateElement attemptBlock, RecoveryBlock recoveryBlock) {
- this.attemptBlock = attemptBlock;
- this.recoveryBlock = recoveryBlock;
- setRegulatedChildBufferCapacity(2);
- addRegulatedChild(attemptBlock);
- addRegulatedChild(recoveryBlock);
+ AttemptBlock(TemplateElements attemptedSectionChildren, RecoveryBlock recoverySection) {
+ TemplateElement attemptedSection = attemptedSectionChildren.asSingleElement();
+ this.attemptedSection = attemptedSection;
+ this.recoverySection = recoverySection;
+ setChildBufferCapacity(2);
+ addChild(attemptedSection); // for backward compatibility
+ addChild(recoverySection);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- env.visitAttemptRecover(attemptBlock, recoveryBlock);
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ env.visitAttemptRecover(this, attemptedSection, recoverySection);
+ return null;
}
@Override
@@ -51,12 +53,7 @@
} else {
StringBuilder buf = new StringBuilder();
buf.append("<").append(getNodeTypeSymbol()).append(">");
- if (attemptBlock != null) {
- buf.append(attemptBlock.getCanonicalForm());
- }
- if (recoveryBlock != null) {
- buf.append(recoveryBlock.getCanonicalForm());
- }
+ buf.append(getChildrenCanonicalForm());
buf.append("</").append(getNodeTypeSymbol()).append(">");
return buf.toString();
}
@@ -70,7 +67,7 @@
@Override
Object getParameterValue(int idx) {
if (idx != 0) throw new IndexOutOfBoundsException();
- return recoveryBlock;
+ return recoverySection;
}
@Override
@@ -85,11 +82,6 @@
}
@Override
- boolean isShownInStackTrace() {
- return false;
- }
-
- @Override
boolean isNestedBlockRepeater() {
return false;
}
diff --git a/src/main/java/freemarker/core/AutoEscBlock.java b/src/main/java/freemarker/core/AutoEscBlock.java
index ce23189..b8a75e2 100644
--- a/src/main/java/freemarker/core/AutoEscBlock.java
+++ b/src/main/java/freemarker/core/AutoEscBlock.java
@@ -28,22 +28,19 @@
*/
final class AutoEscBlock extends TemplateElement {
- AutoEscBlock(TemplateElement nestedBlock) {
- setNestedBlock(nestedBlock);
+ AutoEscBlock(TemplateElements children) {
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitByHiddingParent(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
@Override
protected String dump(boolean canonical) {
if (canonical) {
- String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
- return "<" + getNodeTypeSymbol() + "\">" + nested + "</" + getNodeTypeSymbol() + ">";
+ return "<" + getNodeTypeSymbol() + "\">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
} else {
return getNodeTypeSymbol();
}
@@ -70,8 +67,8 @@
}
@Override
- boolean isIgnorable() {
- return getNestedBlock() == null || getNestedBlock().isIgnorable();
+ boolean isIgnorable(boolean stripWhitespace) {
+ return getChildCount() == 0;
}
@Override
diff --git a/src/main/java/freemarker/core/BlockAssignment.java b/src/main/java/freemarker/core/BlockAssignment.java
index d86c724..6de0c8f 100644
--- a/src/main/java/freemarker/core/BlockAssignment.java
+++ b/src/main/java/freemarker/core/BlockAssignment.java
@@ -40,8 +40,8 @@
private final int scope;
private final MarkupOutputFormat<?> markupOutputFormat;
- BlockAssignment(TemplateElement nestedBlock, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
- setNestedBlock(nestedBlock);
+ BlockAssignment(TemplateElements children, String varName, int scope, Expression namespaceExp, MarkupOutputFormat<?> markupOutputFormat) {
+ setChildren(children);
this.varName = varName;
this.namespaceExp = namespaceExp;
this.scope = scope;
@@ -49,9 +49,10 @@
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitAndTransform(getNestedBlock(), new CaptureOutput(env), null);
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] children = getChildBuffer();
+ if (children != null) {
+ env.visitAndTransform(children, new CaptureOutput(env), null);
} else {
TemplateModel value = capturedStringToModel("");
if (namespaceExp != null) {
@@ -65,6 +66,7 @@
env.setLocalVariable(varName, value);
}
}
+ return null;
}
private TemplateModel capturedStringToModel(String s) throws TemplateModelException {
@@ -134,7 +136,7 @@
}
if (canonical) {
sb.append('>');
- sb.append(getNestedBlock() == null ? "" : getNestedBlock().getCanonicalForm());
+ sb.append(getChildrenCanonicalForm());
sb.append("</");
sb.append(getNodeTypeSymbol());
sb.append('>');
diff --git a/src/main/java/freemarker/core/BodyInstruction.java b/src/main/java/freemarker/core/BodyInstruction.java
index a0795c1..db7527b 100644
--- a/src/main/java/freemarker/core/BodyInstruction.java
+++ b/src/main/java/freemarker/core/BodyInstruction.java
@@ -55,9 +55,10 @@
* I (JR) realized this thanks to some incisive comments from Daniel Dekany.
*/
@Override
- void accept(Environment env) throws IOException, TemplateException {
+ TemplateElement[] accept(Environment env) throws IOException, TemplateException {
Context bodyContext = new Context(env);
env.invokeNestedContent(bodyContext);
+ return null;
}
@Override
@@ -113,6 +114,11 @@
}
*/
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
+
class Context implements LocalContext {
CallableInvocationContext invokingMacroContext;
Environment.Namespace bodyVars;
diff --git a/src/main/java/freemarker/core/BoundCallable.java b/src/main/java/freemarker/core/BoundCallable.java
index 65105c5..1855c08 100644
--- a/src/main/java/freemarker/core/BoundCallable.java
+++ b/src/main/java/freemarker/core/BoundCallable.java
@@ -113,8 +113,8 @@
/** For backward compatibility only; delegates to the {@link UnboundCallable}'s identical method. */
@Override
- void accept(Environment env) throws TemplateException, IOException {
- unboundCallable.accept(env);
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return unboundCallable.accept(env);
}
/** For backward compatibility only; delegates to the {@link UnboundCallable}'s identical method. */
diff --git a/src/main/java/freemarker/core/BreakInstruction.java b/src/main/java/freemarker/core/BreakInstruction.java
index dc76f68..475ed3d 100644
--- a/src/main/java/freemarker/core/BreakInstruction.java
+++ b/src/main/java/freemarker/core/BreakInstruction.java
@@ -25,7 +25,7 @@
final class BreakInstruction extends TemplateElement {
@Override
- void accept(Environment env) {
+ TemplateElement[] accept(Environment env) {
throw Break.INSTANCE;
}
diff --git a/src/main/java/freemarker/core/CallableInvocationContext.java b/src/main/java/freemarker/core/CallableInvocationContext.java
index 1d0f731..d2e204b 100644
--- a/src/main/java/freemarker/core/CallableInvocationContext.java
+++ b/src/main/java/freemarker/core/CallableInvocationContext.java
@@ -19,8 +19,6 @@
package freemarker.core;
-import java.io.IOException;
-import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
@@ -37,20 +35,20 @@
class CallableInvocationContext implements LocalContext {
final UnboundCallable callableDefinition;
final Environment.Namespace localVars;
- final TemplateElement nestedContent;
+ final TemplateElement[] nestedContentBuffer;
final Environment.Namespace nestedContentNamespace;
final Template nestedContentTemplate;
final List nestedContentParameterNames;
- final ArrayList prevLocalContextStack;
+ final LocalContextStack prevLocalContextStack;
final CallableInvocationContext prevMacroContext;
CallableInvocationContext(UnboundCallable callableDefinition,
Environment env,
- TemplateElement nestedContent,
+ TemplateElement[] nestedContentBuffer,
List nestedContentParameterNames) {
this.callableDefinition = callableDefinition;
this.localVars = env.new Namespace();
- this.nestedContent = nestedContent;
+ this.nestedContentBuffer = nestedContentBuffer;
this.nestedContentNamespace = env.getCurrentNamespace();
this.nestedContentTemplate = env.getCurrentTemplate();
this.nestedContentParameterNames = nestedContentParameterNames;
@@ -62,14 +60,6 @@
return callableDefinition;
}
- void invoce(Environment env) throws TemplateException, IOException {
- sanityCheck(env);
- // Set default values for unspecified parameters
- if (callableDefinition.getNestedBlock() != null) {
- env.visit(callableDefinition.getNestedBlock());
- }
- }
-
// Set default parameters, check if all the required parameters are defined.
void sanityCheck(Environment env) throws TemplateException {
boolean resolvedAnArg, hasUnresolvedArg;
diff --git a/src/main/java/freemarker/core/Case.java b/src/main/java/freemarker/core/Case.java
index 0897b2d..4f9c5c8 100644
--- a/src/main/java/freemarker/core/Case.java
+++ b/src/main/java/freemarker/core/Case.java
@@ -19,10 +19,6 @@
package freemarker.core;
-import java.io.IOException;
-
-import freemarker.template.TemplateException;
-
/**
* Represents a case in a switch statement.
*/
@@ -33,17 +29,14 @@
Expression condition;
- Case(Expression matchingValue, TemplateElement nestedBlock) {
+ Case(Expression matchingValue, TemplateElements children) {
this.condition = matchingValue;
- setNestedBlock(nestedBlock);
+ setChildren(children);
}
@Override
- void accept(Environment env)
- throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitByHiddingParent(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) {
+ return getChildBuffer();
}
@Override
@@ -57,7 +50,7 @@
}
if (canonical) {
sb.append('>');
- if (getNestedBlock() != null) sb.append(getNestedBlock().getCanonicalForm());
+ sb.append(getChildrenCanonicalForm());
}
return sb.toString();
}
diff --git a/src/main/java/freemarker/core/Comment.java b/src/main/java/freemarker/core/Comment.java
index 94e8a92..89ac087 100644
--- a/src/main/java/freemarker/core/Comment.java
+++ b/src/main/java/freemarker/core/Comment.java
@@ -37,8 +37,9 @@
}
@Override
- void accept(Environment env) {
+ TemplateElement[] accept(Environment env) {
// do nothing, skip the body
+ return null;
}
@Override
diff --git a/src/main/java/freemarker/core/CompressedBlock.java b/src/main/java/freemarker/core/CompressedBlock.java
index 702b3c8..9a497e3 100644
--- a/src/main/java/freemarker/core/CompressedBlock.java
+++ b/src/main/java/freemarker/core/CompressedBlock.java
@@ -31,22 +31,23 @@
*/
final class CompressedBlock extends TemplateElement {
- CompressedBlock(TemplateElement nestedBlock) {
- setNestedBlock(nestedBlock);
+ CompressedBlock(TemplateElements children) {
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitAndTransform(getNestedBlock(), StandardCompress.INSTANCE, null);
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] childBuffer = getChildBuffer();
+ if (childBuffer != null) {
+ env.visitAndTransform(childBuffer, StandardCompress.INSTANCE, null);
}
+ return null;
}
@Override
protected String dump(boolean canonical) {
if (canonical) {
- String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
- return "<" + getNodeTypeSymbol() + ">" + nested + "</" + getNodeTypeSymbol() + ">";
+ return "<" + getNodeTypeSymbol() + ">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
} else {
return getNodeTypeSymbol();
}
@@ -73,8 +74,8 @@
}
@Override
- boolean isIgnorable() {
- return getNestedBlock() == null || getNestedBlock().isIgnorable();
+ boolean isIgnorable(boolean stripWhitespace) {
+ return getChildCount() == 0 && getParameterCount() == 0;
}
@Override
diff --git a/src/main/java/freemarker/core/ConditionalBlock.java b/src/main/java/freemarker/core/ConditionalBlock.java
index cbc1b40..3b94001 100644
--- a/src/main/java/freemarker/core/ConditionalBlock.java
+++ b/src/main/java/freemarker/core/ConditionalBlock.java
@@ -36,21 +36,19 @@
final Expression condition;
private final int type;
- boolean isLonelyIf;
- ConditionalBlock(Expression condition, TemplateElement nestedBlock, int type) {
+ ConditionalBlock(Expression condition, TemplateElements children, int type) {
this.condition = condition;
- setNestedBlock(nestedBlock);
+ setChildren(children);
this.type = type;
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
if (condition == null || condition.evalToBoolean(env)) {
- if (getNestedBlock() != null) {
- env.visitByHiddingParent(getNestedBlock());
- }
+ return getChildBuffer();
}
+ return null;
}
@Override
@@ -64,10 +62,8 @@
}
if (canonical) {
buf.append(">");
- if (getNestedBlock() != null) {
- buf.append(getNestedBlock().getCanonicalForm());
- }
- if (isLonelyIf) {
+ buf.append(getChildrenCanonicalForm());
+ if (!(getParentElement() instanceof IfBlock)) {
buf.append("</#if>");
}
}
diff --git a/src/main/java/freemarker/core/DebugBreak.java b/src/main/java/freemarker/core/DebugBreak.java
index 53ad26c..32efcac 100644
--- a/src/main/java/freemarker/core/DebugBreak.java
+++ b/src/main/java/freemarker/core/DebugBreak.java
@@ -33,17 +33,17 @@
@Deprecated
public class DebugBreak extends TemplateElement {
public DebugBreak(TemplateElement nestedBlock) {
- setNestedBlock(nestedBlock);
+ addChild(nestedBlock);
copyLocationFrom(nestedBlock);
}
@Override
- protected void accept(Environment env) throws TemplateException, IOException {
+ protected TemplateElement[] accept(Environment env) throws TemplateException, IOException {
if (!DebuggerService.suspendEnvironment(
- env, this.getUnboundTemplate().getSourceName(), getNestedBlock().getBeginLine())) {
- getNestedBlock().accept(env);
+ env, this.getUnboundTemplate().getSourceName(), getChild(0).getBeginLine())) {
+ return getChild(0).accept(env);
} else {
- throw new StopException(env, "Stopped by debugger");
+ throw new StopException(env, "Stopped by debugger");
}
}
@@ -53,11 +53,11 @@
StringBuilder sb = new StringBuilder();
sb.append("<#-- ");
sb.append("debug break");
- if (getNestedBlock() == null) {
+ if (getChildCount() == 0) {
sb.append(" /-->");
} else {
sb.append(" -->");
- sb.append(getNestedBlock().getCanonicalForm());
+ sb.append(getChild(0).getCanonicalForm());
sb.append("<#--/ debug break -->");
}
return sb.toString();
diff --git a/src/main/java/freemarker/core/DollarVariable.java b/src/main/java/freemarker/core/DollarVariable.java
index 2ecab8c..1f7c933 100644
--- a/src/main/java/freemarker/core/DollarVariable.java
+++ b/src/main/java/freemarker/core/DollarVariable.java
@@ -55,7 +55,7 @@
* Outputs the string value of the enclosed expression.
*/
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
final Object moOrStr = calculateInterpolatedStringOrMarkup(env);
final Writer out = env.getOut();
if (moOrStr instanceof String) {
@@ -88,6 +88,7 @@
moOF.output(mo, out);
}
}
+ return null;
}
@Override
diff --git a/src/main/java/freemarker/core/ElseOfList.java b/src/main/java/freemarker/core/ElseOfList.java
index 9741d15..3557e38 100644
--- a/src/main/java/freemarker/core/ElseOfList.java
+++ b/src/main/java/freemarker/core/ElseOfList.java
@@ -28,15 +28,13 @@
*/
final class ElseOfList extends TemplateElement {
- ElseOfList(TemplateElement block) {
- setNestedBlock(block);
+ ElseOfList(TemplateElements children) {
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitByHiddingParent(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
@Override
@@ -44,9 +42,7 @@
if (canonical) {
StringBuilder buf = new StringBuilder();
buf.append('<').append(getNodeTypeSymbol()).append('>');
- if (getNestedBlock() != null) {
- buf.append(getNestedBlock().getCanonicalForm());
- }
+ buf.append(getChildrenCanonicalForm());
return buf.toString();
} else {
return getNodeTypeSymbol();
diff --git a/src/main/java/freemarker/core/Environment.java b/src/main/java/freemarker/core/Environment.java
index 6242cea..55a0ad9 100644
--- a/src/main/java/freemarker/core/Environment.java
+++ b/src/main/java/freemarker/core/Environment.java
@@ -41,6 +41,7 @@
import java.util.Set;
import java.util.TimeZone;
+import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import freemarker.cache.TemplateNameFormat;
import freemarker.cache._CacheAPI;
import freemarker.ext.beans.BeansWrapper;
@@ -110,7 +111,8 @@
private final Configuration configuration;
private final TemplateHashModel rootDataModel;
- private final ArrayList/* <TemplateElement> */ instructionStack = new ArrayList();
+ private TemplateElement[] instructionStack = new TemplateElement[16];
+ private int instructionStackSize = 0;
private final ArrayList recoveredErrorStack = new ArrayList();
private TemplateNumberFormat cachedTemplateNumberFormat;
@@ -160,7 +162,7 @@
private CallableInvocationContext currentMacroContext;
private Writer out;
- private ArrayList localContextStack;
+ private LocalContextStack localContextStack;
private final Namespace mainNamespace;
private Namespace globalNamespace;
private HashMap loadedLibs;
@@ -251,6 +253,7 @@
*
* @since 2.3.23
*/
+ @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False alarm")
public Template getCurrentTemplate() {
return currentTemplate;
}
@@ -263,13 +266,14 @@
*
* @since 2.3.22
*/
+ @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "False alarm")
public DirectiveCallPlace getCurrentDirectiveCallPlace() {
- int ln = instructionStack.size();
+ int ln = instructionStackSize;
if (ln == 0) return null;
- TemplateElement te = (TemplateElement) instructionStack.get(ln - 1);
+ TemplateElement te = instructionStack[ln - 1];
if (te instanceof UnifiedCall) return (UnifiedCall) te;
- if (te instanceof Macro && ln > 1 && instructionStack.get(ln - 2) instanceof UnifiedCall) {
- return (UnifiedCall) instructionStack.get(ln - 2);
+ if (te instanceof Macro && ln > 1 && instructionStack[ln - 2] instanceof UnifiedCall) {
+ return (UnifiedCall) instructionStack[ln - 2];
}
return null;
}
@@ -317,51 +321,89 @@
/**
* "Visit" the template element.
*/
- void visit(TemplateElement element)
- throws TemplateException, IOException {
+ void visit(TemplateElement element) throws IOException, TemplateException {
+ // ATTENTION: This method body is manually "inlined" into visit(TemplateElement[]); keep them in sync!
pushElement(element);
try {
- element.accept(this);
+ TemplateElement[] templateElementsToVisit = element.accept(this);
+ if (templateElementsToVisit != null) {
+ for (TemplateElement el : templateElementsToVisit) {
+ if (el == null) {
+ break; // Skip unused trailing buffer capacity
+ }
+ visit(el);
+ }
+ }
} catch (TemplateException te) {
handleTemplateException(te);
} finally {
popElement();
}
+ // ATTENTION: This method body above is manually "inlined" into visit(TemplateElement[]); keep them in sync!
}
-
+
/**
- * Instead of pushing into the element stack, we replace the top element for the time the parameter element is
- * visited, and then we restore the top element. The main purpose of this is to get rid of elements in the error
- * stack trace that from user perspective shouldn't have a stack frame. The typical example is
- * {@code [#if foo]...[@failsHere/]...[/#if]}, where the #if call shouldn't be in the stack trace. (Simply marking
- * #if as hidden in stack traces would be wrong, because we still want to show #if when its test expression fails.)
+ * @param elementBuffer
+ * The elements to visit; might contains trailing {@code null}-s. Can be {@code null}.
+ *
+ * @since 2.3.24
*/
- void visitByHiddingParent(TemplateElement element)
- throws TemplateException, IOException {
- TemplateElement parent = replaceTopElement(element);
- try {
- element.accept(this);
- } catch (TemplateException te) {
- handleTemplateException(te);
- } finally {
- replaceTopElement(parent);
+ final void visit(TemplateElement[] elementBuffer) throws IOException, TemplateException {
+ if (elementBuffer == null) {
+ return;
+ }
+ for (TemplateElement element : elementBuffer) {
+ if (element == null) {
+ break; // Skip unused trailing buffer capacity
+ }
+
+ // ATTENTION: This part is the manually "inlining" of visit(TemplateElement[]); keep them in sync!
+ // We don't just let Hotspot to do it, as we want a hard guarantee regarding maximum stack usage.
+ pushElement(element);
+ try {
+ TemplateElement[] templateElementsToVisit = element.accept(this);
+ if (templateElementsToVisit != null) {
+ for (TemplateElement el : templateElementsToVisit) {
+ if (el == null) {
+ break; // Skip unused trailing buffer capacity
+ }
+ visit(el);
+ }
+ }
+ } catch (TemplateException te) {
+ handleTemplateException(te);
+ } finally {
+ popElement();
+ }
+ // ATTENTION: This part above is the manually "inlining" of visit(TemplateElement[]); keep them in sync!
}
}
+ @SuppressFBWarnings(value = "RANGE_ARRAY_INDEX", justification = "Not called when stack is empty")
private TemplateElement replaceTopElement(TemplateElement element) {
- return (TemplateElement) instructionStack.set(instructionStack.size() - 1, element);
+ return instructionStack[instructionStackSize - 1] = element;
}
private static final TemplateModel[] NO_OUT_ARGS = new TemplateModel[0];
+ /**
+ * @deprecated Should be internal API
+ */
+ @Deprecated
public void visit(final TemplateElement element,
TemplateDirectiveModel directiveModel, Map args,
final List bodyParameterNames) throws TemplateException, IOException {
+ visit(new TemplateElement[] { element }, directiveModel, args, bodyParameterNames);
+ }
+
+ void visit(final TemplateElement[] childBuffer,
+ TemplateDirectiveModel directiveModel, Map args,
+ final List bodyParameterNames) throws TemplateException, IOException {
TemplateDirectiveBody nested;
- if (element == null) {
+ if (childBuffer == null) {
nested = null;
} else {
- nested = new NestedElementTemplateDirectiveBody(element);
+ nested = new NestedElementTemplateDirectiveBody(childBuffer);
}
final TemplateModel[] outArgs;
if (bodyParameterNames == null || bodyParameterNames.isEmpty()) {
@@ -386,7 +428,7 @@
directiveModel.execute(this, args, outArgs, nested);
} finally {
if (outArgs.length > 0) {
- popLocalContext();
+ localContextStack.pop();
}
}
}
@@ -394,14 +436,14 @@
/**
* "Visit" the template element, passing the output through a TemplateTransformModel
*
- * @param element
- * the element to visit through a transform
+ * @param elementBuffer
+ * the element to visit through a transform; might contains trailing {@code null}-s
* @param transform
* the transform to pass the element output through
* @param args
* optional arguments fed to the transform
*/
- void visitAndTransform(TemplateElement element,
+ void visitAndTransform(TemplateElement[] elementBuffer,
TemplateTransformModel transform,
Map args)
throws TemplateException, IOException {
@@ -417,9 +459,7 @@
try {
if (tc == null || tc.onStart() != TransformControl.SKIP_BODY) {
do {
- if (element != null) {
- visitByHiddingParent(element);
- }
+ visit(elementBuffer);
} while (tc != null && tc.afterBody() == TransformControl.REPEAT_EVALUATION);
}
} catch (Throwable t) {
@@ -452,8 +492,9 @@
/**
* Visit a block using buffering/recovery
*/
- void visitAttemptRecover(TemplateElement attemptBlock, RecoveryBlock recoveryBlock)
- throws TemplateException, IOException {
+ void visitAttemptRecover(
+ AttemptBlock attemptBlock, TemplateElement attemptedSection, RecoveryBlock recoverySection)
+ throws TemplateException, IOException {
Writer prevOut = this.out;
StringWriter sw = new StringWriter();
this.out = sw;
@@ -462,7 +503,7 @@
boolean lastInAttemptBlock = inAttemptBlock;
try {
inAttemptBlock = true;
- visitByHiddingParent(attemptBlock);
+ visit(attemptedSection);
} catch (TemplateException te) {
thrownException = te;
} finally {
@@ -477,7 +518,7 @@
}
try {
recoveredErrorStack.add(thrownException);
- visit(recoveryBlock);
+ visit(recoverySection);
} finally {
recoveredErrorStack.remove(recoveredErrorStack.size() - 1);
}
@@ -509,9 +550,9 @@
*/
void invokeNestedContent(BodyInstruction.Context bodyCtx) throws TemplateException, IOException {
CallableInvocationContext invokingMacroContext = getCurrentMacroContext();
- ArrayList prevLocalContextStack = localContextStack;
- TemplateElement nestedContent = invokingMacroContext.nestedContent;
- if (nestedContent != null) {
+ LocalContextStack prevLocalContextStack = localContextStack;
+ TemplateElement[] nestedContentBuffer = invokingMacroContext.nestedContentBuffer;
+ if (nestedContentBuffer != null) {
this.currentMacroContext = invokingMacroContext.prevMacroContext;
final Namespace prevCurrentNamespace = currentNamespace;
@@ -534,10 +575,10 @@
pushLocalContext(bodyCtx);
}
try {
- visit(nestedContent);
+ visit(nestedContentBuffer);
} finally {
if (invokingMacroContext.nestedContentParameterNames != null) {
- popLocalContext();
+ localContextStack.pop();
}
this.currentMacroContext = invokingMacroContext;
currentNamespace = prevCurrentNamespace;
@@ -564,7 +605,7 @@
handleTemplateException(te);
return true;
} finally {
- popLocalContext();
+ localContextStack.pop();
}
}
@@ -657,7 +698,7 @@
*/
void invoke(BoundCallable boundCallable,
Map namedArgs, List positionalArgs,
- List bodyParameterNames, TemplateElement nestedBlock) throws TemplateException, IOException {
+ List bodyParameterNames, TemplateElement[] childBuffer) throws TemplateException, IOException {
UnboundCallable unboundCallable = boundCallable.getUnboundCallable();
if (unboundCallable == UnboundCallable.NO_OP_MACRO) {
return;
@@ -665,13 +706,13 @@
pushElement(unboundCallable);
try {
- final CallableInvocationContext macroCtx = new CallableInvocationContext(unboundCallable, this, nestedBlock, bodyParameterNames);
+ final CallableInvocationContext macroCtx = new CallableInvocationContext(unboundCallable, this, childBuffer, bodyParameterNames);
setMacroContextLocalsFromArguments(macroCtx, unboundCallable, namedArgs, positionalArgs);
final CallableInvocationContext prevMacroCtx = currentMacroContext;
currentMacroContext = macroCtx;
- final ArrayList prevLocalContextStack = localContextStack;
+ final LocalContextStack prevLocalContextStack = localContextStack;
localContextStack = null;
final Namespace prevCurrentNamespace = currentNamespace;
@@ -681,7 +722,8 @@
currentTemplate = boundCallable.getTemplate();
try {
- macroCtx.invoce(this);
+ macroCtx.sanityCheck(this);
+ visit(unboundCallable.getChildBuffer());
} catch (ReturnInstruction.Return re) {
// Not an error, just a <#return>
} catch (TemplateException te) {
@@ -1830,7 +1872,7 @@
public TemplateModel getLocalVariable(String name) throws TemplateModelException {
if (localContextStack != null) {
for (int i = localContextStack.size() - 1; i >= 0; i--) {
- LocalContext lc = (LocalContext) localContextStack.get(i);
+ LocalContext lc = localContextStack.get(i);
TemplateModel tm = lc.getLocalVariable(name);
if (tm != null) {
return tm;
@@ -1957,7 +1999,7 @@
}
if (localContextStack != null) {
for (int i = localContextStack.size() - 1; i >= 0; i--) {
- LocalContext lc = (LocalContext) localContextStack.get(i);
+ LocalContext lc = localContextStack.get(i);
set.addAll(lc.getLocalVariableNames());
}
}
@@ -2064,11 +2106,11 @@
*/
TemplateElement[] getInstructionStackSnapshot() {
int requiredLength = 0;
- int ln = instructionStack.size();
+ int ln = instructionStackSize;
for (int i = 0; i < ln; i++) {
- TemplateElement stackEl = (TemplateElement) instructionStack.get(i);
- if (i == ln || stackEl.isShownInStackTrace()) {
+ TemplateElement stackEl = instructionStack[i];
+ if (i == ln - 1 || stackEl.isShownInStackTrace()) {
requiredLength++;
}
}
@@ -2078,8 +2120,8 @@
TemplateElement[] result = new TemplateElement[requiredLength];
int dstIdx = requiredLength - 1;
for (int i = 0; i < ln; i++) {
- TemplateElement stackEl = (TemplateElement) instructionStack.get(i);
- if (i == ln || stackEl.isShownInStackTrace()) {
+ TemplateElement stackEl = instructionStack[i];
+ if (i == ln - 1 || stackEl.isShownInStackTrace()) {
result[dstIdx--] = stackEl;
}
}
@@ -2118,16 +2160,12 @@
private void pushLocalContext(LocalContext localContext) {
if (localContextStack == null) {
- localContextStack = new ArrayList();
+ localContextStack = new LocalContextStack();
}
- localContextStack.add(localContext);
+ localContextStack.push(localContext);
}
- private void popLocalContext() {
- localContextStack.remove(localContextStack.size() - 1);
- }
-
- ArrayList getLocalContextStack() {
+ LocalContextStack getLocalContextStack() {
return localContextStack;
}
@@ -2249,15 +2287,25 @@
}
private void pushElement(TemplateElement element) {
- instructionStack.add(element);
+ final int newSize = ++instructionStackSize;
+ TemplateElement[] instructionStack = this.instructionStack;
+ if (newSize > instructionStack.length) {
+ final TemplateElement[] newInstructionStack = new TemplateElement[newSize * 2];
+ for (int i = 0; i < instructionStack.length; i++) {
+ newInstructionStack[i] = instructionStack[i];
+ }
+ instructionStack = newInstructionStack;
+ this.instructionStack = instructionStack;
+ }
+ instructionStack[newSize - 1] = element;
}
private void popElement() {
- instructionStack.remove(instructionStack.size() - 1);
+ instructionStackSize--;
}
void replaceElementStackTop(TemplateElement instr) {
- instructionStack.set(instructionStack.size() - 1, instr);
+ instructionStack[instructionStackSize - 1] = instr;
}
public TemplateNodeModel getCurrentVisitorNode() {
@@ -2686,24 +2734,24 @@
final class NestedElementTemplateDirectiveBody implements TemplateDirectiveBody {
- private final TemplateElement element;
+ private final TemplateElement[] childBuffer;
- private NestedElementTemplateDirectiveBody(TemplateElement element) {
- this.element = element;
+ private NestedElementTemplateDirectiveBody(TemplateElement[] childBuffer) {
+ this.childBuffer = childBuffer;
}
public void render(Writer newOut) throws TemplateException, IOException {
Writer prevOut = out;
out = newOut;
try {
- Environment.this.visit(element);
+ visit(childBuffer);
} finally {
out = prevOut;
}
}
-
- public TemplateElement getElement() {
- return element;
+
+ TemplateElement[] getChildrenBuffer() {
+ return childBuffer;
}
}
diff --git a/src/main/java/freemarker/core/EscapeBlock.java b/src/main/java/freemarker/core/EscapeBlock.java
index da33316..9f17626 100644
--- a/src/main/java/freemarker/core/EscapeBlock.java
+++ b/src/main/java/freemarker/core/EscapeBlock.java
@@ -40,17 +40,15 @@
this.escapedExpr = escapedExpr;
}
- void setContent(TemplateElement nestedBlock) {
- setNestedBlock(nestedBlock);
+ void setContent(TemplateElements children) {
+ setChildren(children);
// We don't need it anymore at this point
this.escapedExpr = null;
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visit(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
Expression doEscape(Expression expression) {
@@ -66,9 +64,7 @@
.append(" as ").append(expr.getCanonicalForm());
if (canonical) {
sb.append('>');
- if (getNestedBlock() != null) {
- sb.append(getNestedBlock().getCanonicalForm());
- }
+ sb.append(getChildrenCanonicalForm());
sb.append("</").append(getNodeTypeSymbol()).append('>');
}
return sb.toString();
@@ -80,11 +76,6 @@
}
@Override
- boolean isShownInStackTrace() {
- return false;
- }
-
- @Override
int getParameterCount() {
return 2;
}
diff --git a/src/main/java/freemarker/core/Expression.java b/src/main/java/freemarker/core/Expression.java
index 10ff7df..fec7090 100644
--- a/src/main/java/freemarker/core/Expression.java
+++ b/src/main/java/freemarker/core/Expression.java
@@ -57,8 +57,7 @@
// Hook in here to set the constant value if possible.
@Override
- void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine)
- throws ParseException {
+ void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine) {
super.setLocation(unboundTemplate, beginColumn, beginLine, endColumn, endLine);
if (isLiteral()) {
try {
diff --git a/src/main/java/freemarker/core/FallbackInstruction.java b/src/main/java/freemarker/core/FallbackInstruction.java
index a86f302..58d6845 100644
--- a/src/main/java/freemarker/core/FallbackInstruction.java
+++ b/src/main/java/freemarker/core/FallbackInstruction.java
@@ -26,8 +26,9 @@
final class FallbackInstruction extends TemplateElement {
@Override
- void accept(Environment env) throws IOException, TemplateException {
+ TemplateElement[] accept(Environment env) throws IOException, TemplateException {
env.fallback();
+ return null;
}
@Override
@@ -60,4 +61,9 @@
return false;
}
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
+
}
diff --git a/src/main/java/freemarker/core/FlushInstruction.java b/src/main/java/freemarker/core/FlushInstruction.java
index 1657395..ff1338a 100644
--- a/src/main/java/freemarker/core/FlushInstruction.java
+++ b/src/main/java/freemarker/core/FlushInstruction.java
@@ -27,8 +27,9 @@
final class FlushInstruction extends TemplateElement {
@Override
- void accept(Environment env) throws IOException {
+ TemplateElement[] accept(Environment env) throws IOException {
env.getOut().flush();
+ return null;
}
@Override
diff --git a/src/main/java/freemarker/core/IfBlock.java b/src/main/java/freemarker/core/IfBlock.java
index 0bb476f..69b3484 100644
--- a/src/main/java/freemarker/core/IfBlock.java
+++ b/src/main/java/freemarker/core/IfBlock.java
@@ -31,36 +31,33 @@
final class IfBlock extends TemplateElement {
IfBlock(ConditionalBlock block) {
- setRegulatedChildBufferCapacity(1);
+ setChildBufferCapacity(1);
addBlock(block);
}
void addBlock(ConditionalBlock block) {
- addRegulatedChild(block);
+ addChild(block);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- int ln = getRegulatedChildCount();
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ int ln = getChildCount();
for (int i = 0; i < ln; i++) {
- ConditionalBlock cblock = (ConditionalBlock) getRegulatedChild(i);
+ ConditionalBlock cblock = (ConditionalBlock) getChild(i);
Expression condition = cblock.condition;
env.replaceElementStackTop(cblock);
if (condition == null || condition.evalToBoolean(env)) {
- if (cblock.getNestedBlock() != null) {
- env.visitByHiddingParent(cblock.getNestedBlock());
- }
- return;
+ return cblock.getChildBuffer();
}
}
+ return null;
}
@Override
TemplateElement postParseCleanup(boolean stripWhitespace)
throws ParseException {
- if (getRegulatedChildCount() == 1) {
- ConditionalBlock cblock = (ConditionalBlock) getRegulatedChild(0);
- cblock.isLonelyIf = true;
+ if (getChildCount() == 1) {
+ ConditionalBlock cblock = (ConditionalBlock) getChild(0);
cblock.setLocation(getUnboundTemplate(), cblock, this);
return cblock.postParseCleanup(stripWhitespace);
} else {
@@ -72,9 +69,9 @@
protected String dump(boolean canonical) {
if (canonical) {
StringBuilder buf = new StringBuilder();
- int ln = getRegulatedChildCount();
+ int ln = getChildCount();
for (int i = 0; i < ln; i++) {
- ConditionalBlock cblock = (ConditionalBlock) getRegulatedChild(i);
+ ConditionalBlock cblock = (ConditionalBlock) getChild(i);
buf.append(cblock.dump(canonical));
}
buf.append("</#if>");
@@ -103,11 +100,6 @@
ParameterRole getParameterRole(int idx) {
throw new IndexOutOfBoundsException();
}
-
- @Override
- boolean isShownInStackTrace() {
- return false;
- }
@Override
boolean isNestedBlockRepeater() {
diff --git a/src/main/java/freemarker/core/Include.java b/src/main/java/freemarker/core/Include.java
index fe36924..755a32f 100644
--- a/src/main/java/freemarker/core/Include.java
+++ b/src/main/java/freemarker/core/Include.java
@@ -118,7 +118,7 @@
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
final String includedTemplateName = includedTemplateNameExp.evalAndCoerceToPlainText(env);
final String fullIncludedTemplateName;
try {
@@ -170,6 +170,7 @@
if (includedTemplate != null) {
env.include(includedTemplate);
}
+ return null;
}
@Override
@@ -249,4 +250,10 @@
return true;
}
*/
+
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
+
}
diff --git a/src/main/java/freemarker/core/Items.java b/src/main/java/freemarker/core/Items.java
index eecc15f..38bc5a6 100644
--- a/src/main/java/freemarker/core/Items.java
+++ b/src/main/java/freemarker/core/Items.java
@@ -30,13 +30,13 @@
private final String loopVarName;
- public Items(String loopVariableName, TemplateElement nestedBlock) {
+ Items(String loopVariableName, TemplateElements children) {
this.loopVarName = loopVariableName;
- setNestedBlock(nestedBlock);
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
final IterationContext iterCtx = IteratorBlock.findEnclosingIterationContext(env, null);
if (iterCtx == null) {
// The parser should prevent this situation
@@ -44,7 +44,8 @@
getNodeTypeSymbol(), " without iteraton in context");
}
- iterCtx.loopForItemsElement(env, getNestedBlock(), loopVarName);
+ iterCtx.loopForItemsElement(env, getChildBuffer(), loopVarName);
+ return null;
}
@Override
@@ -61,7 +62,7 @@
sb.append(loopVarName);
if (canonical) {
sb.append('>');
- if (getNestedBlock() != null) sb.append(getNestedBlock().getCanonicalForm());
+ sb.append(getChildrenCanonicalForm());
sb.append("</");
sb.append(getNodeTypeSymbol());
sb.append('>');
diff --git a/src/main/java/freemarker/core/IteratorBlock.java b/src/main/java/freemarker/core/IteratorBlock.java
index 22b2164..2a4df44 100644
--- a/src/main/java/freemarker/core/IteratorBlock.java
+++ b/src/main/java/freemarker/core/IteratorBlock.java
@@ -35,7 +35,7 @@
import freemarker.template.utility.Constants;
/**
- * A #list or #foreach element.
+ * A #list (or #foreach) element, or pre-#else section of it inside a {@link ListElseContainer}.
*/
final class IteratorBlock extends TemplateElement {
@@ -48,23 +48,24 @@
* a variable referring to a sequence or collection ("the list" from now on)
* @param loopVarName
* The name of the variable that will hold the value of the current item when looping through the list.
- * @param nestedBlock
+ * @param childrenBeforeElse
* The nested content to execute if the list wasn't empty; can't be {@code null}. If the loop variable
* was specified in the start tag, this is also what we will iterator over.
*/
IteratorBlock(Expression listExp,
String loopVarName,
- TemplateElement nestedBlock,
+ TemplateElements childrenBeforeElse,
boolean isForEach) {
this.listExp = listExp;
this.loopVarName = loopVarName;
- setNestedBlock(nestedBlock);
+ setChildren(childrenBeforeElse);
this.isForEach = isForEach;
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
acceptWithResult(env);
+ return null;
}
boolean acceptWithResult(Environment env) throws TemplateException, IOException {
@@ -88,7 +89,7 @@
*/
static IterationContext findEnclosingIterationContext(Environment env, String loopVariableName)
throws _MiscTemplateException {
- ArrayList ctxStack = env.getLocalContextStack();
+ LocalContextStack ctxStack = env.getLocalContextStack();
if (ctxStack != null) {
for (int i = ctxStack.size() - 1; i >= 0; i--) {
Object ctx = ctxStack.get(i);
@@ -121,9 +122,7 @@
}
if (canonical) {
buf.append(">");
- if (getNestedBlock() != null) {
- buf.append(getNestedBlock().getCanonicalForm());
- }
+ buf.append(getChildrenCanonicalForm());
if (!(getParentElement() instanceof ListElseContainer)) {
buf.append("</");
buf.append(getNodeTypeSymbol());
@@ -198,10 +197,10 @@
}
boolean accept(Environment env) throws TemplateException, IOException {
- return executeNestedBlock(env, getNestedBlock());
+ return executeNestedContent(env, getChildBuffer());
}
- void loopForItemsElement(Environment env, TemplateElement nestedBlock, String loopVarName)
+ void loopForItemsElement(Environment env, TemplateElement[] childBuffer, String loopVarName)
throws NonSequenceOrCollectionException, TemplateModelException, InvalidReferenceException,
TemplateException, IOException {
try {
@@ -211,7 +210,7 @@
}
alreadyEntered = true;
this.loopVarName = loopVarName;
- executeNestedBlock(env, nestedBlock);
+ executeNestedContent(env, childBuffer);
} finally {
this.loopVarName = null;
}
@@ -221,13 +220,7 @@
* Executes the given block for the {@link #listValue}: if {@link #loopVarName} is non-{@code null}, then for
* each list item once, otherwise once if {@link #listValue} isn't empty.
*/
- private boolean executeNestedBlock(Environment env, TemplateElement nestedBlock)
- throws TemplateModelException, TemplateException, IOException,
- NonSequenceOrCollectionException, InvalidReferenceException {
- return executeNestedBlockInner(env, nestedBlock);
- }
-
- private boolean executeNestedBlockInner(Environment env, TemplateElement nestedBlock)
+ private boolean executeNestedContent(Environment env, TemplateElement[] childBuffer)
throws TemplateModelException, TemplateException, IOException, NonSequenceOrCollectionException,
InvalidReferenceException {
final boolean listNotEmpty;
@@ -243,9 +236,7 @@
while (hasNext) {
loopVar = iterModel.next();
hasNext = iterModel.hasNext();
- if (nestedBlock != null) {
- env.visitByHiddingParent(nestedBlock);
- }
+ env.visit(childBuffer);
index++;
}
} catch (BreakInstruction.Break br) {
@@ -256,9 +247,7 @@
// We must reuse this later, because TemplateCollectionModel-s that wrap an Iterator only
// allow one iterator() call.
openedIteratorModel = iterModel;
- if (nestedBlock != null) {
- env.visitByHiddingParent(nestedBlock);
- }
+ env.visit(childBuffer);
}
}
} else if (listValue instanceof TemplateSequenceModel) {
@@ -271,17 +260,13 @@
for (index = 0; index < size; index++) {
loopVar = seqModel.get(index);
hasNext = (size > index + 1);
- if (nestedBlock != null) {
- env.visitByHiddingParent(nestedBlock);
- }
+ env.visit(childBuffer);
}
} catch (BreakInstruction.Break br) {
// Silently exit loop
}
} else {
- if (nestedBlock != null) {
- env.visitByHiddingParent(nestedBlock);
- }
+ env.visit(childBuffer);
}
}
} else if (env.isClassicCompatible()) {
@@ -291,9 +276,7 @@
hasNext = false;
}
try {
- if (nestedBlock != null) {
- env.visitByHiddingParent(nestedBlock);
- }
+ env.visit(childBuffer);
} catch (BreakInstruction.Break br) {
// Silently exit "loop"
}
diff --git a/src/main/java/freemarker/core/LibraryLoad.java b/src/main/java/freemarker/core/LibraryLoad.java
index 98a7c38..4597539 100644
--- a/src/main/java/freemarker/core/LibraryLoad.java
+++ b/src/main/java/freemarker/core/LibraryLoad.java
@@ -50,7 +50,7 @@
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
final String importedTemplateName = importedTemplateNameExp.evalAndCoerceToPlainText(env);
final String fullImportedTemplateName;
try {
@@ -71,6 +71,7 @@
"):\n", new _DelayedGetMessage(e));
}
env.importLib(importedTemplate, namespace);
+ return null;
}
@Override
@@ -122,4 +123,10 @@
boolean isNestedBlockRepeater() {
return false;
}
+
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
+
}
diff --git a/src/main/java/freemarker/core/ListElseContainer.java b/src/main/java/freemarker/core/ListElseContainer.java
index 34a67a9..1065e9e 100644
--- a/src/main/java/freemarker/core/ListElseContainer.java
+++ b/src/main/java/freemarker/core/ListElseContainer.java
@@ -28,18 +28,19 @@
private final ElseOfList elsePart;
public ListElseContainer(IteratorBlock listPart, ElseOfList elsePart) {
- setRegulatedChildBufferCapacity(2);
- addRegulatedChild(listPart);
- addRegulatedChild(elsePart);
+ setChildBufferCapacity(2);
+ addChild(listPart);
+ addChild(elsePart);
this.listPart = listPart;
this.elsePart = elsePart;
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
if (!listPart.acceptWithResult(env)) {
- elsePart.accept(env);
+ return elsePart.accept(env);
}
+ return null;
}
@Override
@@ -51,9 +52,9 @@
protected String dump(boolean canonical) {
if (canonical) {
StringBuilder buf = new StringBuilder();
- int ln = getRegulatedChildCount();
+ int ln = getChildCount();
for (int i = 0; i < ln; i++) {
- TemplateElement element = getRegulatedChild(i);
+ TemplateElement element = getChild(i);
buf.append(element.dump(canonical));
}
buf.append("</#list>");
diff --git a/src/main/java/freemarker/core/LocalContextStack.java b/src/main/java/freemarker/core/LocalContextStack.java
new file mode 100644
index 0000000..5f556bf
--- /dev/null
+++ b/src/main/java/freemarker/core/LocalContextStack.java
@@ -0,0 +1,39 @@
+package freemarker.core;
+
+/**
+ * Class that's a little bit more efficient than using an {@code ArrayList<LocalContext>}.
+ *
+ * @since 2.3.24
+ */
+final class LocalContextStack {
+
+ private LocalContext[] buffer = new LocalContext[8];
+ private int size;
+
+ void push(LocalContext localContext) {
+ final int newSize = ++size;
+ LocalContext[] buffer = this.buffer;
+ if (buffer.length < newSize) {
+ final LocalContext[] newBuffer = new LocalContext[newSize * 2];
+ for (int i = 0; i < buffer.length; i++) {
+ newBuffer[i] = buffer[i];
+ }
+ buffer = newBuffer;
+ this.buffer = newBuffer;
+ }
+ buffer[newSize - 1] = localContext;
+ }
+
+ void pop() {
+ buffer[--size] = null;
+ }
+
+ public LocalContext get(int index) {
+ return buffer[index];
+ }
+
+ public int size() {
+ return size;
+ }
+
+}
diff --git a/src/main/java/freemarker/core/MixedContent.java b/src/main/java/freemarker/core/MixedContent.java
index c7a4c6e..3306988 100644
--- a/src/main/java/freemarker/core/MixedContent.java
+++ b/src/main/java/freemarker/core/MixedContent.java
@@ -29,20 +29,28 @@
final class MixedContent extends TemplateElement {
MixedContent() { }
-
+
+ /**
+ * @deprecated Use {@link #addChild(TemplateElement)} instead.
+ */
+ @Deprecated
void addElement(TemplateElement element) {
- addRegulatedChild(element);
+ addChild(element);
}
+ /**
+ * @deprecated Use {@link #addChild(int, TemplateElement)} instead.
+ */
+ @Deprecated
void addElement(int index, TemplateElement element) {
- addRegulatedChild(index, element);
+ addChild(index, element);
}
@Override
TemplateElement postParseCleanup(boolean stripWhitespace)
throws ParseException {
super.postParseCleanup(stripWhitespace);
- return getRegulatedChildCount() == 1 ? getRegulatedChild(0) : this;
+ return getChildCount() == 1 ? getChild(0) : this;
}
/**
@@ -50,23 +58,15 @@
* and outputs the resulting text.
*/
@Override
- void accept(Environment env)
+ TemplateElement[] accept(Environment env)
throws TemplateException, IOException {
- int ln = getRegulatedChildCount();
- for (int i = 0; i < ln; i++) {
- env.visit(getRegulatedChild(i));
- }
+ return getChildBuffer();
}
@Override
protected String dump(boolean canonical) {
if (canonical) {
- StringBuilder buf = new StringBuilder();
- int ln = getRegulatedChildCount();
- for (int i = 0; i < ln; i++) {
- buf.append(getRegulatedChild(i).getCanonicalForm());
- }
- return buf.toString();
+ return getChildrenCanonicalForm();
} else {
if (getParentElement() == null) {
return "root";
@@ -77,9 +77,9 @@
@Override
protected boolean isOutputCacheable() {
- int ln = getRegulatedChildCount();
+ int ln = getChildCount();
for (int i = 0; i < ln; i++) {
- if (!getRegulatedChild(i).isOutputCacheable()) {
+ if (!getChild(i).isOutputCacheable()) {
return false;
}
}
@@ -107,13 +107,8 @@
}
@Override
- boolean isShownInStackTrace() {
- return false;
- }
-
- @Override
- boolean isIgnorable() {
- return getRegulatedChildCount() == 0;
+ boolean isIgnorable(boolean stripWhitespace) {
+ return getChildCount() == 0;
}
@Override
diff --git a/src/main/java/freemarker/core/NestedContentNotSupportedException.java b/src/main/java/freemarker/core/NestedContentNotSupportedException.java
index 926fab1..a667af1 100644
--- a/src/main/java/freemarker/core/NestedContentNotSupportedException.java
+++ b/src/main/java/freemarker/core/NestedContentNotSupportedException.java
@@ -37,8 +37,9 @@
return;
}
if (body instanceof NestedElementTemplateDirectiveBody) {
- TemplateElement te = ((NestedElementTemplateDirectiveBody) body).getElement();
- if (te == null || te instanceof ThreadInterruptionCheck) {
+ TemplateElement[] tes = ((NestedElementTemplateDirectiveBody) body).getChildrenBuffer();
+ if (tes == null || tes.length == 0
+ || tes[0] instanceof ThreadInterruptionCheck && (tes.length == 1 || tes[1] == null)) {
return;
}
}
diff --git a/src/main/java/freemarker/core/NoAutoEscBlock.java b/src/main/java/freemarker/core/NoAutoEscBlock.java
index d59c368..7e07bca 100644
--- a/src/main/java/freemarker/core/NoAutoEscBlock.java
+++ b/src/main/java/freemarker/core/NoAutoEscBlock.java
@@ -28,22 +28,19 @@
*/
final class NoAutoEscBlock extends TemplateElement {
- NoAutoEscBlock(TemplateElement nestedBlock) {
- setNestedBlock(nestedBlock);
+ NoAutoEscBlock(TemplateElements children) {
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitByHiddingParent(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
@Override
protected String dump(boolean canonical) {
if (canonical) {
- String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
- return "<" + getNodeTypeSymbol() + "\">" + nested + "</" + getNodeTypeSymbol() + ">";
+ return "<" + getNodeTypeSymbol() + "\">" + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
} else {
return getNodeTypeSymbol();
}
@@ -70,8 +67,8 @@
}
@Override
- boolean isIgnorable() {
- return getNestedBlock() == null || getNestedBlock().isIgnorable();
+ boolean isIgnorable(boolean stripWhitespace) {
+ return getChildCount() == 0;
}
@Override
diff --git a/src/main/java/freemarker/core/NoEscapeBlock.java b/src/main/java/freemarker/core/NoEscapeBlock.java
index f9d94bd..6ac3960 100644
--- a/src/main/java/freemarker/core/NoEscapeBlock.java
+++ b/src/main/java/freemarker/core/NoEscapeBlock.java
@@ -27,21 +27,19 @@
*/
class NoEscapeBlock extends TemplateElement {
- NoEscapeBlock(TemplateElement nestedBlock) {
- setNestedBlock(nestedBlock);
+ NoEscapeBlock(TemplateElements children) {
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visit(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
@Override
protected String dump(boolean canonical) {
if (canonical) {
- return "<" + getNodeTypeSymbol() + '>' + getNestedBlock().getCanonicalForm()
+ return "<" + getNodeTypeSymbol() + '>' + getChildrenCanonicalForm()
+ "</" + getNodeTypeSymbol() + '>';
} else {
return getNodeTypeSymbol();
diff --git a/src/main/java/freemarker/core/NumericalOutput.java b/src/main/java/freemarker/core/NumericalOutput.java
index c9028c6..3e6e53a 100644
--- a/src/main/java/freemarker/core/NumericalOutput.java
+++ b/src/main/java/freemarker/core/NumericalOutput.java
@@ -59,7 +59,7 @@
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
String s = calculateInterpolatedStringOrMarkup(env);
Writer out = env.getOut();
if (autoEscapeOutputFormat != null) {
@@ -67,6 +67,7 @@
} else {
out.write(s);
}
+ return null;
}
@Override
diff --git a/src/main/java/freemarker/core/OutputFormatBlock.java b/src/main/java/freemarker/core/OutputFormatBlock.java
index 12afb93..1b5a746 100644
--- a/src/main/java/freemarker/core/OutputFormatBlock.java
+++ b/src/main/java/freemarker/core/OutputFormatBlock.java
@@ -30,24 +30,21 @@
private final Expression paramExp;
- OutputFormatBlock(TemplateElement nestedBlock, Expression paramExp) {
+ OutputFormatBlock(TemplateElements children, Expression paramExp) {
this.paramExp = paramExp;
- setNestedBlock(nestedBlock);
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitByHiddingParent(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
@Override
protected String dump(boolean canonical) {
if (canonical) {
- String nested = getNestedBlock() != null ? getNestedBlock().getCanonicalForm() : "";
return "<" + getNodeTypeSymbol() + " \"" + paramExp.getCanonicalForm() + "\">"
- + nested + "</" + getNodeTypeSymbol() + ">";
+ + getChildrenCanonicalForm() + "</" + getNodeTypeSymbol() + ">";
} else {
return getNodeTypeSymbol();
}
@@ -78,8 +75,8 @@
}
@Override
- boolean isIgnorable() {
- return getNestedBlock() == null || getNestedBlock().isIgnorable();
+ boolean isIgnorable(boolean stripWhitespace) {
+ return getChildCount() == 0;
}
@Override
diff --git a/src/main/java/freemarker/core/PropertySetting.java b/src/main/java/freemarker/core/PropertySetting.java
index 86309da..e975f84 100644
--- a/src/main/java/freemarker/core/PropertySetting.java
+++ b/src/main/java/freemarker/core/PropertySetting.java
@@ -110,7 +110,7 @@
}
@Override
- void accept(Environment env) throws TemplateException {
+ TemplateElement[] accept(Environment env) throws TemplateException {
TemplateModel mval = value.eval(env);
String strval;
if (mval instanceof TemplateScalarModel) {
@@ -123,6 +123,7 @@
strval = value.evalAndCoerceToStringOrUnsupportedMarkup(env);
}
env.setSetting(key, strval);
+ return null;
}
@Override
diff --git a/src/main/java/freemarker/core/RecoveryBlock.java b/src/main/java/freemarker/core/RecoveryBlock.java
index 3e44468..67ff1fb 100644
--- a/src/main/java/freemarker/core/RecoveryBlock.java
+++ b/src/main/java/freemarker/core/RecoveryBlock.java
@@ -25,15 +25,13 @@
final class RecoveryBlock extends TemplateElement {
- RecoveryBlock(TemplateElement block) {
- setNestedBlock(block);
+ RecoveryBlock(TemplateElements children) {
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
- if (getNestedBlock() != null) {
- env.visitByHiddingParent(getNestedBlock());
- }
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
+ return getChildBuffer();
}
@Override
@@ -41,9 +39,7 @@
if (canonical) {
StringBuilder buf = new StringBuilder();
buf.append('<').append(getNodeTypeSymbol()).append('>');
- if (getNestedBlock() != null) {
- buf.append(getNestedBlock().getCanonicalForm());
- }
+ buf.append(getChildrenCanonicalForm());
return buf.toString();
} else {
return getNodeTypeSymbol();
diff --git a/src/main/java/freemarker/core/RecurseNode.java b/src/main/java/freemarker/core/RecurseNode.java
index 456727b..1c15da8 100644
--- a/src/main/java/freemarker/core/RecurseNode.java
+++ b/src/main/java/freemarker/core/RecurseNode.java
@@ -43,7 +43,7 @@
}
@Override
- void accept(Environment env) throws IOException, TemplateException {
+ TemplateElement[] accept(Environment env) throws IOException, TemplateException {
TemplateModel node = targetNode == null ? null : targetNode.eval(env);
if (node != null && !(node instanceof TemplateNodeModel)) {
throw new NonNodeException(targetNode, node, "node", env);
@@ -71,6 +71,7 @@
}
env.recurse((TemplateNodeModel) node, (TemplateSequenceModel) nss);
+ return null;
}
@Override
@@ -123,4 +124,9 @@
return false;
}
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
+
}
diff --git a/src/main/java/freemarker/core/ReturnInstruction.java b/src/main/java/freemarker/core/ReturnInstruction.java
index b3d0574..51feab7 100644
--- a/src/main/java/freemarker/core/ReturnInstruction.java
+++ b/src/main/java/freemarker/core/ReturnInstruction.java
@@ -33,18 +33,15 @@
}
@Override
- void accept(Environment env) throws TemplateException {
+ TemplateElement[] accept(Environment env) throws TemplateException {
if (exp != null) {
env.setLastReturnValue(exp.eval(env));
}
- if (nextSibling() != null) {
- // We need to jump out using an exception.
- throw Return.INSTANCE;
+ if (nextSibling() == null && getParentElement() instanceof Macro) {
+ // Avoid unnecessary exception throwing
+ return null;
}
- if (!(getParentElement() instanceof Macro || getParentElement().getParentElement() instanceof Macro)) {
- // Here also, we need to jump out using an exception.
- throw Return.INSTANCE;
- }
+ throw Return.INSTANCE;
}
@Override
diff --git a/src/main/java/freemarker/core/Sep.java b/src/main/java/freemarker/core/Sep.java
index 7550656..0ae5de2 100644
--- a/src/main/java/freemarker/core/Sep.java
+++ b/src/main/java/freemarker/core/Sep.java
@@ -28,12 +28,12 @@
*/
class Sep extends TemplateElement {
- public Sep(TemplateElement nestedBlock) {
- setNestedBlock(nestedBlock);
+ public Sep(TemplateElements children) {
+ setChildren(children);
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
final IterationContext iterCtx = IteratorBlock.findEnclosingIterationContext(env, null);
if (iterCtx == null) {
// The parser should prevent this situation
@@ -42,8 +42,9 @@
}
if (iterCtx.hasNext()) {
- env.visitByHiddingParent(getNestedBlock());
+ return getChildBuffer();
}
+ return null;
}
@Override
@@ -58,7 +59,7 @@
sb.append(getNodeTypeSymbol());
if (canonical) {
sb.append('>');
- if (getNestedBlock() != null) sb.append(getNestedBlock().getCanonicalForm());
+ sb.append(getChildrenCanonicalForm());
sb.append("</");
sb.append(getNodeTypeSymbol());
sb.append('>');
diff --git a/src/main/java/freemarker/core/StackTraceVisibility.java b/src/main/java/freemarker/core/StackTraceVisibility.java
new file mode 100644
index 0000000..6fcd877
--- /dev/null
+++ b/src/main/java/freemarker/core/StackTraceVisibility.java
@@ -0,0 +1,8 @@
+package freemarker.core;
+
+
+public enum StackTraceVisibility {
+
+ ALWAYS, WHEN_ON_TOP, NEVER
+
+}
diff --git a/src/main/java/freemarker/core/StopInstruction.java b/src/main/java/freemarker/core/StopInstruction.java
index c6a7526..590d9f1 100644
--- a/src/main/java/freemarker/core/StopInstruction.java
+++ b/src/main/java/freemarker/core/StopInstruction.java
@@ -33,7 +33,7 @@
}
@Override
- void accept(Environment env) throws TemplateException {
+ TemplateElement[] accept(Environment env) throws TemplateException {
if (exp == null) {
throw new StopException(env);
}
diff --git a/src/main/java/freemarker/core/SwitchBlock.java b/src/main/java/freemarker/core/SwitchBlock.java
index 284aa02..2cef5ce 100644
--- a/src/main/java/freemarker/core/SwitchBlock.java
+++ b/src/main/java/freemarker/core/SwitchBlock.java
@@ -36,7 +36,7 @@
*/
SwitchBlock(Expression searched) {
this.searched = searched;
- setRegulatedChildBufferCapacity(4);
+ setChildBufferCapacity(4);
}
/**
@@ -46,17 +46,17 @@
if (cas.condition == null) {
defaultCase = cas;
}
- addRegulatedChild(cas);
+ addChild(cas);
}
@Override
- void accept(Environment env)
+ TemplateElement[] accept(Environment env)
throws TemplateException, IOException {
boolean processedCase = false;
- int ln = getRegulatedChildCount();
+ int ln = getChildCount();
try {
for (int i = 0; i < ln; i++) {
- Case cas = (Case) getRegulatedChild(i);
+ Case cas = (Case) getChild(i);
boolean processCase = false;
// Fall through if a previous case tested true.
@@ -69,7 +69,7 @@
EvalUtil.CMP_OP_EQUALS, "case==", cas.condition, cas.condition, env);
}
if (processCase) {
- env.visitByHiddingParent(cas);
+ env.visit(cas);
processedCase = true;
}
}
@@ -77,9 +77,10 @@
// If we didn't process any nestedElements, and we have a default,
// process it.
if (!processedCase && defaultCase != null) {
- env.visitByHiddingParent(defaultCase);
+ env.visit(defaultCase);
}
} catch (BreakInstruction.Break br) {}
+ return null;
}
@Override
@@ -91,9 +92,9 @@
buf.append(searched.getCanonicalForm());
if (canonical) {
buf.append('>');
- int ln = getRegulatedChildCount();
+ int ln = getChildCount();
for (int i = 0; i < ln; i++) {
- Case cas = (Case) getRegulatedChild(i);
+ Case cas = (Case) getChild(i);
buf.append(cas.getCanonicalForm());
}
buf.append("</").append(getNodeTypeSymbol()).append('>');
diff --git a/src/main/java/freemarker/core/TemplateElement.java b/src/main/java/freemarker/core/TemplateElement.java
index 07407f3..916769e 100644
--- a/src/main/java/freemarker/core/TemplateElement.java
+++ b/src/main/java/freemarker/core/TemplateElement.java
@@ -31,8 +31,7 @@
/**
* <b>Internal API - subject to change:</b> Represent directive call, interpolation, text block, or other such
* non-expression node in the parsed template. Some information that can be found here can be accessed through the
- * {@link Environment#getCurrentDirectiveCallPlace()}, which a published API, and thus promises backward
- * compatibility.
+ * {@link Environment#getCurrentDirectiveCallPlace()}, which a published API, and thus promises backward compatibility.
*
* @deprecated This is an internal FreeMarker API with no backward compatibility guarantees, so you shouldn't depend on
* it.
@@ -45,44 +44,44 @@
private TemplateElement parent;
/**
- * Used by elements that has no fixed schema for its child elements. For example, a {@code #case} can enclose any
- * kind of elements. Only one of {@link #nestedBlock} and {@link #regulatedChildBuffer} can be non-{@code null}.
- * This element is typically a {@link MixedContent}, at least before {@link #postParseCleanup(boolean)} (which
- * optimizes out {@link MixedContent} with child count less than 2).
+ * Contains 1 or more nested elements with optional trailing {@code null}-s, or is {@code null} exactly if there are
+ * no nested elements.
*/
- private TemplateElement nestedBlock;
-
- /**
- * Used by elements that has a fixed schema for its child elements. For example, {@code #switch} can only have
- * {@code #case} and {@code #default} child elements. Only one of {@link #nestedBlock} and
- * {@link #regulatedChildBuffer} can be non-{@code null}.
- */
- private TemplateElement[] regulatedChildBuffer;
- private int regulatedChildCount;
+ private TemplateElement[] childBuffer;
/**
- * The index of the element in the parent's {@link #regulatedChildBuffer} array, or 0 if this is the
- * {@link #nestedBlock} of the parent.
+ * Contains the number of elements in the {@link #childBuffer}, not counting the trailing {@code null}-s. If this is
+ * 0, then and only then {@link #childBuffer} must be {@code null}.
+ */
+ private int childCount;
+
+ /**
+ * The index of the element in the parent's {@link #childBuffer} array.
*
* @since 2.3.23
*/
private int index;
/**
- * Processes the contents of this <tt>TemplateElement</tt> and
- * outputs the resulting text
+ * Executes this {@link TemplateElement}. Usually should not be called directly, but through
+ * {@link Environment#visit(TemplateElement)} or a similar {@link Environment} method.
*
- * @param env The runtime environment
+ * @param env
+ * The runtime environment
+ *
+ * @return The template elements to execute (meant to be used for nested elements), or {@code null}. Can have
+ * <em>trailing</em> {@code null}-s (unused buffer capacity). Returning the nested elements instead of
+ * executing them inside this method is a trick used for decreasing stack usage when there's nothing to do
+ * after the children was processed anyway.
*/
- abstract void accept(Environment env) throws TemplateException, IOException;
+ abstract TemplateElement[] accept(Environment env) throws TemplateException, IOException;
/**
- * One-line description of the element, that contain 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.
+ * One-line description of the element, that contain 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.
*
* This final implementation calls {@link #dump(boolean) dump(false)}.
*
@@ -92,7 +91,7 @@
public final String getDescription() {
return dump(false);
}
-
+
/**
* This final implementation calls {@link #dump(boolean) dump(false)}.
*/
@@ -100,74 +99,88 @@
public final String getCanonicalForm() {
return dump(true);
}
-
- /**
- * Tells if the element should show up in error stack traces. If you think you need to set this to {@code false} for
- * an element, always consider if you should use {@link Environment#visitByHiddingParent(TemplateElement)} instead.
- *
- * Note that this will be ignored for the top (current) element of a stack trace, as that's always shown.
- */
- boolean isShownInStackTrace() {
- return true;
+
+ final String getChildrenCanonicalForm() {
+ return getChildrenCanonicalForm(childBuffer);
}
+ static String getChildrenCanonicalForm(TemplateElement[] children) {
+ if (children == null) {
+ return "";
+ }
+ StringBuilder sb = new StringBuilder();
+ for (TemplateElement child : children) {
+ if (child == null) {
+ break;
+ }
+ sb.append(child.getCanonicalForm());
+ }
+ return sb.toString();
+ }
+
/**
- * Tells if this element possibly executes its {@link #nestedBlock} for many times. This flag is useful when
- * a template AST is modified for running time limiting (see {@link ThreadInterruptionSupportTemplatePostProcessor}).
- * Elements that use {@link #regulatedChildBuffer} should not need this, as the insertion of the timeout checks is
- * impossible there, given their rigid nested element schema.
+ * Tells if the element should show up in error stack traces. Note that this will be ignored for the top (current)
+ * element of a stack trace, as that's always shown.
+ */
+ boolean isShownInStackTrace() {
+ return false;
+ }
+
+ /**
+ * Tells if this element possibly executes its nested content for many times. This flag is useful when a template
+ * AST is modified for running time limiting (see {@link ThreadInterruptionSupportTemplatePostProcessor}). Elements
+ * that use {@link #childBuffer} should not need this, as the insertion of the timeout checks is impossible there,
+ * given their rigid nested element schema.
*/
abstract boolean isNestedBlockRepeater();
/**
- * Brings the implementation of {@link #getCanonicalForm()} and {@link #getDescription()} to a single place.
- * Don't call those methods in method on {@code this}, because that will result in infinite recursion!
+ * Brings the implementation of {@link #getCanonicalForm()} and {@link #getDescription()} to a single place. Don't
+ * call those methods in method on {@code this}, because that will result in infinite recursion!
*
- * @param canonical if {@code true}, it calculates the return value of {@link #getCanonicalForm()},
- * otherwise of {@link #getDescription()}.
+ * @param canonical
+ * if {@code true}, it calculates the return value of {@link #getCanonicalForm()}, otherwise of
+ * {@link #getDescription()}.
*/
abstract protected String dump(boolean canonical);
-
-// Methods to implement TemplateNodeModel
+
+ // Methods to implement TemplateNodeModel
public TemplateNodeModel getParentNode() {
-// return parent;
- return null;
+ // return parent;
+ return null;
}
-
+
public String getNodeNamespace() {
return null;
}
-
+
public String getNodeType() {
return "element";
}
-
+
public TemplateSequenceModel getChildNodes() {
- if (regulatedChildBuffer != null) {
- final SimpleSequence seq = new SimpleSequence(regulatedChildCount);
- for (int i = 0; i < regulatedChildCount; i++) {
- seq.add(regulatedChildBuffer[i]);
+ if (childBuffer != null) {
+ final SimpleSequence seq = new SimpleSequence(childCount);
+ for (int i = 0; i < childCount; i++) {
+ seq.add(childBuffer[i]);
}
return seq;
+ } else {
+ return new SimpleSequence(0);
}
- SimpleSequence result = new SimpleSequence(1);
- if (nestedBlock != null) {
- result.add(nestedBlock);
- }
- return result;
}
-
+
public String getNodeName() {
String classname = this.getClass().getName();
int shortNameOffset = classname.lastIndexOf('.') + 1;
return classname.substring(shortNameOffset);
}
-
- // Methods so that we can implement the Swing TreeNode API.
+
+ // Methods so that we can implement the Swing TreeNode API.
public boolean isLeaf() {
- return nestedBlock == null && regulatedChildCount == 0;
+ return childCount == 0;
}
/**
@@ -179,31 +192,16 @@
}
public int getIndex(TemplateElement node) {
- if (nestedBlock instanceof MixedContent) {
- return nestedBlock.getIndex(node);
- }
- if (nestedBlock != null) {
- if (node == nestedBlock) {
- return 0;
- }
- } else {
- for (int i = 0; i < regulatedChildCount; i++) {
- if (regulatedChildBuffer[i].equals(node)) {
- return i;
- }
+ for (int i = 0; i < childCount; i++) {
+ if (childBuffer[i].equals(node)) {
+ return i;
}
}
return -1;
}
public int getChildCount() {
- if (nestedBlock instanceof MixedContent) {
- return nestedBlock.getChildCount();
- }
- if (nestedBlock != null) {
- return 1;
- }
- return regulatedChildCount;
+ return childCount;
}
/**
@@ -211,57 +209,37 @@
* {@link MixedContent}.
*/
public Enumeration children() {
- if (nestedBlock instanceof MixedContent) {
- return nestedBlock.children();
- }
- if (nestedBlock != null) {
- return Collections.enumeration(Collections.singletonList(nestedBlock));
- } else if (regulatedChildBuffer != null) {
- return new _ArrayEnumeration(regulatedChildBuffer, regulatedChildCount);
- }
- return Collections.enumeration(Collections.EMPTY_LIST);
+ return childBuffer != null
+ ? new _ArrayEnumeration(childBuffer, childCount)
+ : Collections.enumeration(Collections.EMPTY_LIST);
}
+ /**
+ * @deprecated Internal API - even internally, use {@link #getChild(int)} instead.
+ */
+ @Deprecated
public TemplateElement getChildAt(int index) {
- if (nestedBlock instanceof MixedContent) {
- return nestedBlock.getChildAt(index);
+ if (childCount == 0) {
+ throw new IndexOutOfBoundsException("Template element has no children");
}
- if (nestedBlock != null) {
- if (index == 0) {
- return nestedBlock;
- }
- throw new ArrayIndexOutOfBoundsException("invalid index");
- } else if (regulatedChildCount != 0) {
- try {
- return regulatedChildBuffer[index];
- } catch (ArrayIndexOutOfBoundsException e) {
- // nestedElements was a List earlier, so we emulate the same kind of exception
- throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + regulatedChildCount);
- }
+ try {
+ return childBuffer[index];
+ } catch (ArrayIndexOutOfBoundsException e) {
+ // nestedElements was a List earlier, so we emulate the same kind of exception
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + childCount);
}
- throw new ArrayIndexOutOfBoundsException("Template element has no children");
}
public void setChildAt(int index, TemplateElement element) {
- if (nestedBlock instanceof MixedContent) {
- nestedBlock.setChildAt(index, element);
- } else if (nestedBlock != null) {
- if (index == 0) {
- nestedBlock = element;
- element.index = 0;
- element.parent = this;
- } else {
- throw new IndexOutOfBoundsException("invalid index");
- }
- } else if (regulatedChildBuffer != null) {
- regulatedChildBuffer[index] = element;
+ if (index < childCount && index >= 0) {
+ childBuffer[index] = element;
element.index = index;
element.parent = this;
} else {
- throw new IndexOutOfBoundsException("element has no children");
+ throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + childCount);
}
}
-
+
/**
* The element whose child this element is, or {@code null} if this is the root node.
*
@@ -272,29 +250,42 @@
return parent;
}
- final void setRegulatedChildBufferCapacity(int capacity) {
- int ln = regulatedChildCount;
- TemplateElement[] newRegulatedChildBuffer = new TemplateElement[capacity];
- for (int i = 0; i < ln; i++) {
- newRegulatedChildBuffer[i] = regulatedChildBuffer[i];
- }
- regulatedChildBuffer = newRegulatedChildBuffer;
- }
-
- final void addRegulatedChild(TemplateElement nestedElement) {
- addRegulatedChild(regulatedChildCount, nestedElement);
+ /**
+ * The element whose child this element is, or {@code null} if this is the root node.
+ */
+ final TemplateElement getParentElement() {
+ return parent;
}
- final void addRegulatedChild(int index, TemplateElement nestedElement) {
- final int lRegulatedChildCount = regulatedChildCount;
-
- TemplateElement[] lRegulatedChildBuffer = regulatedChildBuffer;
+ final void setChildBufferCapacity(int capacity) {
+ int ln = childCount;
+ TemplateElement[] newRegulatedChildBuffer = new TemplateElement[capacity];
+ for (int i = 0; i < ln; i++) {
+ newRegulatedChildBuffer[i] = childBuffer[i];
+ }
+ childBuffer = newRegulatedChildBuffer;
+ }
+
+ /**
+ * Inserts a new nested element after the last nested element.
+ */
+ final void addChild(TemplateElement nestedElement) {
+ addChild(childCount, nestedElement);
+ }
+
+ /**
+ * Inserts a new nested element at the given index, which can also be one higher than the current highest index.
+ */
+ final void addChild(int index, TemplateElement nestedElement) {
+ final int lRegulatedChildCount = childCount;
+
+ TemplateElement[] lRegulatedChildBuffer = childBuffer;
if (lRegulatedChildBuffer == null) {
lRegulatedChildBuffer = new TemplateElement[INITIAL_REGULATED_CHILD_BUFFER_CAPACITY];
- regulatedChildBuffer = lRegulatedChildBuffer;
+ childBuffer = lRegulatedChildBuffer;
} else if (lRegulatedChildCount == lRegulatedChildBuffer.length) {
- setRegulatedChildBufferCapacity(lRegulatedChildCount != 0 ? lRegulatedChildCount * 2 : 1);
- lRegulatedChildBuffer = regulatedChildBuffer;
+ setChildBufferCapacity(lRegulatedChildCount != 0 ? lRegulatedChildCount * 2 : 1);
+ lRegulatedChildBuffer = childBuffer;
}
// At this point: nestedElements == this.nestedElements, and has sufficient capacity.
@@ -306,40 +297,42 @@
nestedElement.index = index;
nestedElement.parent = this;
lRegulatedChildBuffer[index] = nestedElement;
- regulatedChildCount = lRegulatedChildCount + 1;
+ childCount = lRegulatedChildCount + 1;
}
-
- final int getRegulatedChildCount() {
- return regulatedChildCount;
+
+ final TemplateElement getChild(int index) {
+ return childBuffer[index];
}
-
- final TemplateElement getRegulatedChild(int index) {
- return regulatedChildBuffer[index];
+
+ /**
+ * @return Array containing 1 or more nested elements with optional trailing {@code null}-s, or is {@code null}
+ * exactly if there are no nested elements.
+ */
+ final TemplateElement[] getChildBuffer() {
+ return childBuffer;
}
-
+
+ /**
+ * @param buffWithCnt Maybe {@code null}
+ *
+ * @since 2.3.24
+ */
+ final void setChildren(TemplateElements buffWithCnt) {
+ TemplateElement[] childBuffer = buffWithCnt.getBuffer();
+ int childCount = buffWithCnt.getCount();
+ for (int i = 0; i < childCount; i++) {
+ TemplateElement child = childBuffer[i];
+ child.index = i;
+ child.parent = this;
+ }
+ this.childBuffer = childBuffer;
+ this.childCount = childCount;
+ }
+
final int getIndex() {
return index;
}
-
- /**
- * The element whose child this element is, or {@code null} if this is the root node.
- */
- final TemplateElement getParentElement() {
- return parent;
- }
-
- final TemplateElement getNestedBlock() {
- return nestedBlock;
- }
- final void setNestedBlock(TemplateElement nestedBlock) {
- if (nestedBlock != null) {
- nestedBlock.parent = this;
- nestedBlock.index = 0;
- }
- this.nestedBlock = nestedBlock;
- }
-
/**
* This is a special case, because a root element is not contained in another element, so we couldn't set the
* private fields.
@@ -350,7 +343,7 @@
}
/**
- * Walk the AST subtree rooted by this element, and do simplifications where possible, also remove superfluous
+ * Walk the AST subtree rooted by this element, and do simplifications where possible, also removes superfluous
* whitespace.
*
* @param stripWhitespace
@@ -361,56 +354,63 @@
* is the duty of the caller, not of this method.
*/
TemplateElement postParseCleanup(boolean stripWhitespace) throws ParseException {
- int regulatedChildCount = this.regulatedChildCount;
- if (regulatedChildCount != 0) {
- for (int i = 0; i < regulatedChildCount; i++) {
- TemplateElement te = regulatedChildBuffer[i];
+ int childCount = this.childCount;
+ if (childCount != 0) {
+ for (int i = 0; i < childCount; i++) {
+ TemplateElement te = childBuffer[i];
+
+ /*
+ // Assertion:
+ if (te.getIndex() != i) {
+ throw new BugException("Invalid index " + te.getIndex() + " (expected: "
+ + i + ") for: " + te.dump(false));
+ }
+ if (te.getParent() != this) {
+ throw new BugException("Invalid parent " + te.getParent() + " (expected: "
+ + this.dump(false) + ") for: " + te.dump(false));
+ }
+ */
+
te = te.postParseCleanup(stripWhitespace);
- regulatedChildBuffer[i] = te;
+ childBuffer[i] = te;
te.parent = this;
te.index = i;
}
- if (stripWhitespace) {
- for (int i = 0; i < regulatedChildCount; i++) {
- TemplateElement te = regulatedChildBuffer[i];
- if (te.isIgnorable()) {
- regulatedChildCount--;
- for (int j = i; j < regulatedChildCount; j++) {
- final TemplateElement te2 = regulatedChildBuffer[j + 1];
- regulatedChildBuffer[j] = te2;
- te2.index = j;
- }
- regulatedChildBuffer[regulatedChildCount] = null;
- this.regulatedChildCount = regulatedChildCount;
- i--;
+ for (int i = 0; i < childCount; i++) {
+ TemplateElement te = childBuffer[i];
+ if (te.isIgnorable(stripWhitespace)) {
+ childCount--;
+ // As later isIgnorable calls might investigates the siblings, we have to move all the items now.
+ for (int j = i; j < childCount; j++) {
+ final TemplateElement te2 = childBuffer[j + 1];
+ childBuffer[j] = te2;
+ te2.index = j;
}
+ childBuffer[childCount] = null;
+ this.childCount = childCount;
+ i--;
}
}
- if (regulatedChildCount < regulatedChildBuffer.length
- && regulatedChildCount <= regulatedChildBuffer.length * 3 / 4) {
- TemplateElement[] trimmedregulatedChildBuffer = new TemplateElement[regulatedChildCount];
- for (int i = 0; i < regulatedChildCount; i++) {
- trimmedregulatedChildBuffer[i] = regulatedChildBuffer[i];
+ if (childCount == 0) {
+ childBuffer = null;
+ } else if (childCount < childBuffer.length
+ && childCount <= childBuffer.length * 3 / 4) {
+ TemplateElement[] trimmedChildBuffer = new TemplateElement[childCount];
+ for (int i = 0; i < childCount; i++) {
+ trimmedChildBuffer[i] = childBuffer[i];
}
- regulatedChildBuffer = trimmedregulatedChildBuffer;
- }
- } else if (nestedBlock != null) {
- nestedBlock = nestedBlock.postParseCleanup(stripWhitespace);
- if (nestedBlock.isIgnorable()) {
- nestedBlock = null;
- } else {
- nestedBlock.parent = this;
+ childBuffer = trimmedChildBuffer;
}
}
return this;
}
- boolean isIgnorable() {
+ boolean isIgnorable(boolean stripWhitespace) {
return false;
}
-// The following methods exist to support some fancier tree-walking
-// and were introduced to support the whitespace cleanup feature in 2.2
+ // The following methods exist to support some fancier tree-walking
+ // and were introduced to support the whitespace cleanup feature in 2.2
TemplateElement prevTerminalNode() {
TemplateElement prev = previousSibling();
@@ -436,41 +436,29 @@
if (parent == null) {
return null;
}
- return index > 0 ? parent.regulatedChildBuffer[index - 1] : null;
+ return index > 0 ? parent.childBuffer[index - 1] : null;
}
TemplateElement nextSibling() {
if (parent == null) {
return null;
}
- return index + 1 < parent.regulatedChildCount ? parent.regulatedChildBuffer[index + 1] : null;
+ return index + 1 < parent.childCount ? parent.childBuffer[index + 1] : null;
}
private TemplateElement getFirstChild() {
- if (nestedBlock != null) {
- return nestedBlock;
- }
- if (regulatedChildCount == 0) {
- return null;
- }
- return regulatedChildBuffer[0];
+ return childCount == 0 ? null : childBuffer[0];
}
private TemplateElement getLastChild() {
- if (nestedBlock != null) {
- return nestedBlock;
- }
- final int regulatedChildCount = this.regulatedChildCount;
- if (regulatedChildCount == 0) {
- return null;
- }
- return regulatedChildBuffer[regulatedChildCount - 1];
+ final int regulatedChildCount = this.childCount;
+ return regulatedChildCount == 0 ? null : childBuffer[regulatedChildCount - 1];
}
private TemplateElement getFirstLeaf() {
TemplateElement te = this;
while (!te.isLeaf() && !(te instanceof Macro) && !(te instanceof BlockAssignment)) {
- // A macro or macro invocation is treated as a leaf here for special reasons
+ // A macro or macro invocation is treated as a leaf here for special reasons
te = te.getFirstChild();
}
return te;
@@ -492,20 +480,28 @@
boolean isOutputCacheable() {
return false;
}
-
+
+ boolean isChildrenOutputCacheable() {
+ int ln = childCount;
+ for (int i = 0; i < ln; i++) {
+ if (!childBuffer[i].isOutputCacheable()) {
+ return false;
+ }
+ }
+ return true;
+ }
+
/**
- * determines whether this element's presence on a line
- * indicates that we should not strip opening whitespace
- * in the post-parse whitespace gobbling step.
+ * determines whether this element's presence on a line indicates that we should not strip opening whitespace in the
+ * post-parse whitespace gobbling step.
*/
boolean heedsOpeningWhitespace() {
return false;
}
/**
- * determines whether this element's presence on a line
- * indicates that we should not strip trailing whitespace
- * in the post-parse whitespace gobbling step.
+ * determines whether this element's presence on a line indicates that we should not strip trailing whitespace in
+ * the post-parse whitespace gobbling step.
*/
boolean heedsTrailingWhitespace() {
return false;
diff --git a/src/main/java/freemarker/core/TemplateElementArrayBuilder.java b/src/main/java/freemarker/core/TemplateElementArrayBuilder.java
new file mode 100644
index 0000000..70a990b
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateElementArrayBuilder.java
@@ -0,0 +1,80 @@
+package freemarker.core;
+
+import freemarker.template.utility.CollectionUtils;
+
+/**
+ * Holds an buffer (array) of {@link TemplateElement}-s with the count of the utilized items in it. The un-utilized tail
+ * of the array must only contain {@code null}-s.
+ *
+ * @since 2.3.24
+ */
+class TemplateElements {
+
+ static final TemplateElements EMPTY = new TemplateElements(null, 0);
+
+ private final TemplateElement[] buffer;
+ private final int count;
+
+ /**
+ * @param buffer
+ * The buffer; {@code null} exactly if {@code count} is 0.
+ * @param count
+ * The number of utilized buffer elements; if 0, then {@code null} must be {@code null}.
+ */
+ TemplateElements(TemplateElement[] buffer, int count) {
+ /*
+ // Assertion:
+ if (count == 0 && buffer != null) {
+ throw new IllegalArgumentException();
+ }
+ */
+
+ this.buffer = buffer;
+ this.count = count;
+ }
+
+ TemplateElement[] getBuffer() {
+ return buffer;
+ }
+
+ int getCount() {
+ return count;
+ }
+
+ TemplateElement getLast() {
+ return buffer != null ? buffer[count - 1] : null;
+ }
+
+ /**
+ * Used for some backward compatibility hacks.
+ */
+ TemplateElement asSingleElement() {
+ if (count == 0) {
+ return new TextBlock(CollectionUtils.EMPTY_CHAR_ARRAY, false);
+ } else {
+ TemplateElement first = buffer[0];
+ if (count == 1) {
+ return first;
+ } else {
+ MixedContent mixedContent = new MixedContent();
+ mixedContent.setChildren(this);
+ mixedContent.setLocation(first.getUnboundTemplate(), first, getLast());
+ return mixedContent;
+ }
+ }
+ }
+
+ /**
+ * Used for some backward compatibility hacks.
+ */
+ MixedContent asMixedContent() {
+ MixedContent mixedContent = new MixedContent();
+ if (count != 0) {
+ TemplateElement first = buffer[0];
+ mixedContent.setChildren(this);
+ mixedContent.setLocation(first.getUnboundTemplate(), first, getLast());
+ }
+ return mixedContent;
+ }
+
+}
diff --git a/src/main/java/freemarker/core/TemplateElementsToVisit.java b/src/main/java/freemarker/core/TemplateElementsToVisit.java
new file mode 100644
index 0000000..6159f13
--- /dev/null
+++ b/src/main/java/freemarker/core/TemplateElementsToVisit.java
@@ -0,0 +1,30 @@
+package freemarker.core;
+
+import java.util.Collection;
+import java.util.Collections;
+
+/**
+ * Used as the return value of {@link TemplateElement#accept(Environment)} when the invoked element has nested elements
+ * to invoke. It would be more natural to invoke child elements before returning from
+ * {@link TemplateElement#accept(Environment)}, however, if there's nothing to do after the child elements were invoked,
+ * that would mean wasting stack space.
+ *
+ * @since 2.3.24
+ */
+class TemplateElementsToVisit {
+
+ private final Collection<TemplateElement> templateElements;
+
+ TemplateElementsToVisit(Collection<TemplateElement> templateElements) {
+ this.templateElements = null != templateElements ? templateElements : Collections.<TemplateElement> emptyList();
+ }
+
+ TemplateElementsToVisit(TemplateElement nestedBlock) {
+ this(Collections.singleton(nestedBlock));
+ }
+
+ Collection<TemplateElement> getTemplateElements() {
+ return templateElements;
+ }
+
+}
diff --git a/src/main/java/freemarker/core/TemplateObject.java b/src/main/java/freemarker/core/TemplateObject.java
index 2333645..805af1c 100644
--- a/src/main/java/freemarker/core/TemplateObject.java
+++ b/src/main/java/freemarker/core/TemplateObject.java
@@ -19,6 +19,7 @@
package freemarker.core;
+import freemarker.template.Template;
/**
* <b>Internal API - subject to change:</b> Represent a node in the parsed template (either a {@link Expression} or a
@@ -41,28 +42,34 @@
* by a negative line numbers, starting from this constant as line 1. */
static final int RUNTIME_EVAL_LINE_DISPLACEMENT = -1000000000;
- final void setLocation(UnboundTemplate unboundTemplate, Token begin, Token end)
- throws ParseException {
+ final void setLocation(UnboundTemplate unboundTemplate, Token begin, Token end) {
setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
}
- final void setLocation(UnboundTemplate unboundTemplate, Token begin, TemplateObject end)
- throws ParseException {
+ final void setLocation(UnboundTemplate unboundTemplate, Token tagBegin, Token tagEnd, TemplateElements children) {
+ TemplateElement lastChild = children.getLast();
+ if (lastChild != null) {
+ // [<#if exp>children]<#else>
+ setLocation(unboundTemplate, tagBegin, lastChild);
+ } else {
+ // [<#if exp>]<#else>
+ setLocation(unboundTemplate, tagBegin, tagEnd);
+ }
+ }
+
+ final void setLocation(UnboundTemplate unboundTemplate, Token begin, TemplateObject end) {
+ setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
+ }
+
+ final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, Token end) {
setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
}
- final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, Token end)
- throws ParseException {
+ final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, TemplateObject end) {
setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
}
- final void setLocation(UnboundTemplate unboundTemplate, TemplateObject begin, TemplateObject end)
- throws ParseException {
- setLocation(unboundTemplate, begin.beginColumn, begin.beginLine, end.endColumn, end.endLine);
- }
-
- void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine)
- throws ParseException {
+ void setLocation(UnboundTemplate unboundTemplate, int beginColumn, int beginLine, int endColumn, int endLine) {
this.unboundTemplate = unboundTemplate;
this.beginColumn = beginColumn;
this.beginLine = beginLine;
diff --git a/src/main/java/freemarker/core/TextBlock.java b/src/main/java/freemarker/core/TextBlock.java
index 688ddae..3a7c78f 100644
--- a/src/main/java/freemarker/core/TextBlock.java
+++ b/src/main/java/freemarker/core/TextBlock.java
@@ -21,14 +21,14 @@
import java.io.IOException;
+import freemarker.template.utility.CollectionUtils;
import freemarker.template.utility.StringUtil;
/**
* A TemplateElement representing a block of plain text.
*/
public final class TextBlock extends TemplateElement {
- private static final char[] EMPTY_CHAR_ARRAY = new char[0];
- static final TextBlock EMPTY_BLOCK = new TextBlock(EMPTY_CHAR_ARRAY, false);
+
// We're using char[] instead of String for storing the text block because
// Writer.write(String) involves copying the String contents to a char[]
// using String.getChars(), and then calling Writer.write(char[]). By
@@ -45,7 +45,7 @@
this(text.toCharArray(), unparsed);
}
- private TextBlock(char[] text, boolean unparsed) {
+ TextBlock(char[] text, boolean unparsed) {
this.text = text;
this.unparsed = unparsed;
}
@@ -58,9 +58,10 @@
* Simply outputs the text.
*/
@Override
- public void accept(Environment env)
+ public TemplateElement[] accept(Environment env)
throws IOException {
env.getOut().write(text);
+ return null;
}
@Override
@@ -107,7 +108,10 @@
if (!stripWhitespace || text.length == 0 ) {
return this;
}
- if (getParentElement().getParentElement() == null && previousSibling() == null) return this;
+ TemplateElement parentElement = getParentElement();
+ if (isTopLevelTextIfParentIs(parentElement) && previousSibling() == null) {
+ return this;
+ }
if (!deliberateLeftTrim) {
trailingCharsToStrip = trailingCharsToStrip();
}
@@ -217,7 +221,7 @@
break;
}
}
- if (trimTrailingPart) trailingPart = EMPTY_CHAR_ARRAY;
+ if (trimTrailingPart) trailingPart = CollectionUtils.EMPTY_CHAR_ARRAY;
}
this.text = concat(printablePart, trailingPart);
}
@@ -305,7 +309,7 @@
@Override
boolean heedsTrailingWhitespace() {
- if (isIgnorable()) {
+ if (isIgnorable(true)) {
return false;
}
for (int i = 0; i < text.length; i++) {
@@ -322,7 +326,7 @@
@Override
boolean heedsOpeningWhitespace() {
- if (isIgnorable()) {
+ if (isIgnorable(true)) {
return false;
}
for (int i = text.length - 1; i >= 0; i--) {
@@ -338,18 +342,28 @@
}
@Override
- boolean isIgnorable() {
+ boolean isIgnorable(boolean stripWhitespace) {
if (text == null || text.length == 0) {
return true;
}
- if (!StringUtil.isTrimmableToEmpty(text)) {
+ if (stripWhitespace) {
+ if (!StringUtil.isTrimmableToEmpty(text)) {
+ return false;
+ }
+ TemplateElement parentElement = getParentElement();
+ boolean atTopLevel = isTopLevelTextIfParentIs(parentElement);
+ TemplateElement prevSibling = previousSibling();
+ TemplateElement nextSibling = nextSibling();
+ return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
+ && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
+ } else {
return false;
}
- boolean atTopLevel = (getParentElement().getParentElement() == null);
- TemplateElement prevSibling = previousSibling();
- TemplateElement nextSibling = nextSibling();
- return ((prevSibling == null && atTopLevel) || nonOutputtingType(prevSibling))
- && ((nextSibling == null && atTopLevel) || nonOutputtingType(nextSibling));
+ }
+
+ private boolean isTopLevelTextIfParentIs(TemplateElement parentElement) {
+ return parentElement == null
+ || parentElement.getParentElement() == null && parentElement instanceof MixedContent;
}
diff --git a/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java b/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
index b17dc99..51ce3fc 100644
--- a/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
+++ b/src/main/java/freemarker/core/ThreadInterruptionSupportTemplatePostProcessor.java
@@ -58,41 +58,14 @@
return;
}
- final TemplateElement nestedBlock = te.getNestedBlock();
-
- // Deepest-first recursion:
- if (nestedBlock != null) {
- addInterruptionChecks(nestedBlock);
- }
- final int regulatedChildrenCount = te.getRegulatedChildCount();
+ final int regulatedChildrenCount = te.getChildCount();
for (int i = 0; i < regulatedChildrenCount; i++) {
- addInterruptionChecks(te.getRegulatedChild(i));
+ addInterruptionChecks(te.getChild(i));
}
- // Because nestedElements (means fixed schema for the children) and nestedBlock (means no fixed schema) are
- // mutually exclusive, and we only care about the last kind:
if (te.isNestedBlockRepeater()) {
- if (regulatedChildrenCount != 0) {
- // Only elements that use nestedBlock instead of regulatedChildren should be block repeaters.
- // Note that nestedBlock and nestedElements are (should be) mutually exclusive.
- throw new BugException();
- }
try {
- final ThreadInterruptionCheck interruptedChk = new ThreadInterruptionCheck(te);
- if (nestedBlock == null) {
- te.setNestedBlock(interruptedChk);
- } else {
- final MixedContent nestedMixedC;
- if (nestedBlock instanceof MixedContent) {
- nestedMixedC = (MixedContent) nestedBlock;
- } else {
- nestedMixedC = new MixedContent();
- nestedMixedC.setLocation(te.getUnboundTemplate(), 0, 0, 0, 0);
- nestedMixedC.addElement(nestedBlock);
- te.setNestedBlock(nestedMixedC);
- }
- nestedMixedC.addElement(0, interruptedChk);
- }
+ te.addChild(0, new ThreadInterruptionCheck(te));
} catch (ParseException e) {
throw new TemplatePostProcessorException("Unexpected error; see cause", e);
}
@@ -110,12 +83,13 @@
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
// As the API doesn't allow throwing InterruptedException here (nor anywhere else, most importantly,
// Template.process can't throw it), we must not clear the "interrupted" flag of the thread.
if (Thread.currentThread().isInterrupted()) {
throw new TemplateProcessingThreadInterruptedException();
}
+ return null;
}
@Override
diff --git a/src/main/java/freemarker/core/TransformBlock.java b/src/main/java/freemarker/core/TransformBlock.java
index f7a1db9..5cfddc4 100644
--- a/src/main/java/freemarker/core/TransformBlock.java
+++ b/src/main/java/freemarker/core/TransformBlock.java
@@ -48,14 +48,14 @@
*/
TransformBlock(Expression transformExpression,
Map namedArgs,
- TemplateElement nestedBlock) {
+ TemplateElements children) {
this.transformExpression = transformExpression;
this.namedArgs = namedArgs;
- setNestedBlock(nestedBlock);
+ setChildren(children);
}
@Override
- void accept(Environment env)
+ TemplateElement[] accept(Environment env)
throws TemplateException, IOException {
TemplateTransformModel ttm = env.getTransform(transformExpression);
if (ttm != null) {
@@ -72,13 +72,14 @@
} else {
args = EmptyMap.instance;
}
- env.visitAndTransform(getNestedBlock(), ttm, args);
+ env.visitAndTransform(getChildBuffer(), ttm, args);
} else {
TemplateModel tm = transformExpression.eval(env);
throw new UnexpectedTypeException(
transformExpression, tm,
"transform", new Class[] { TemplateTransformModel.class }, env);
}
+ return null;
}
@Override
@@ -99,9 +100,7 @@
}
if (canonical) {
sb.append(">");
- if (getNestedBlock() != null) {
- sb.append(getNestedBlock().getCanonicalForm());
- }
+ sb.append(getChildrenCanonicalForm());
sb.append("</").append(getNodeTypeSymbol()).append('>');
}
return sb.toString();
@@ -160,5 +159,10 @@
boolean isNestedBlockRepeater() {
return false;
}
+
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
}
diff --git a/src/main/java/freemarker/core/TrimInstruction.java b/src/main/java/freemarker/core/TrimInstruction.java
index 053c5a0..7e48367 100644
--- a/src/main/java/freemarker/core/TrimInstruction.java
+++ b/src/main/java/freemarker/core/TrimInstruction.java
@@ -38,8 +38,9 @@
}
@Override
- void accept(Environment env) {
+ TemplateElement[] accept(Environment env) {
// This instruction does nothing at render-time, only parse-time.
+ return null;
}
@Override
@@ -65,7 +66,7 @@
}
@Override
- boolean isIgnorable() {
+ boolean isIgnorable(boolean stripWhitespace) {
return true;
}
diff --git a/src/main/java/freemarker/core/UnboundCallable.java b/src/main/java/freemarker/core/UnboundCallable.java
index 61e3da9..4066818 100644
--- a/src/main/java/freemarker/core/UnboundCallable.java
+++ b/src/main/java/freemarker/core/UnboundCallable.java
@@ -45,7 +45,7 @@
Collections.EMPTY_LIST,
Collections.EMPTY_MAP,
null, false,
- TextBlock.EMPTY_BLOCK);
+ TemplateElements.EMPTY);
final static int TYPE_MACRO = 0;
final static int TYPE_FUNCTION = 1;
@@ -58,7 +58,7 @@
UnboundCallable(String name, List argumentNames, Map args,
String catchAllParamName, boolean function,
- TemplateElement nestedBlock) {
+ TemplateElements children) {
this.name = name;
this.paramNames = (String[]) argumentNames.toArray(
new String[argumentNames.size()]);
@@ -67,7 +67,7 @@
this.function = function;
this.catchAllParamName = catchAllParamName;
- this.setNestedBlock(nestedBlock);
+ this.setChildren(children);
}
String[] getParamNames() {
@@ -102,8 +102,9 @@
}
@Override
- void accept(Environment env) {
+ TemplateElement[] accept(Environment env) {
env.visitCallableDefinition(this);
+ return null;
}
@Override
@@ -149,9 +150,7 @@
if (function) sb.append(')');
if (canonical) {
sb.append('>');
- if (getNestedBlock() != null) {
- sb.append(getNestedBlock().getCanonicalForm());
- }
+ sb.append(getChildrenCanonicalForm());
sb.append("</").append(getNodeTypeSymbol()).append('>');
}
return sb.toString();
diff --git a/src/main/java/freemarker/core/UnifiedCall.java b/src/main/java/freemarker/core/UnifiedCall.java
index 3c0ce94..04a6d3a 100644
--- a/src/main/java/freemarker/core/UnifiedCall.java
+++ b/src/main/java/freemarker/core/UnifiedCall.java
@@ -50,31 +50,28 @@
UnifiedCall(Expression nameExp,
Map namedArgs,
- TemplateElement nestedBlock,
+ TemplateElements children,
List bodyParameterNames) {
this.nameExp = nameExp;
this.namedArgs = namedArgs;
- setNestedBlock(nestedBlock);
+ setChildren(children);
this.bodyParameterNames = bodyParameterNames;
}
UnifiedCall(Expression nameExp,
List positionalArgs,
- TemplateElement nestedBlock,
+ TemplateElements children,
List bodyParameterNames) {
this.nameExp = nameExp;
this.positionalArgs = positionalArgs;
- if (nestedBlock == TextBlock.EMPTY_BLOCK) {
- nestedBlock = null;
- }
- setNestedBlock(nestedBlock);
+ setChildren(children);
this.bodyParameterNames = bodyParameterNames;
}
@Override
- void accept(Environment env) throws TemplateException, IOException {
+ TemplateElement[] accept(Environment env) throws TemplateException, IOException {
final TemplateModel tm = nameExp.eval(env);
- if (tm == UnboundCallable.NO_OP_MACRO) return; // shortcut here.
+ if (tm == UnboundCallable.NO_OP_MACRO) return null; // shortcut here.
if (tm instanceof BoundCallable) {
final BoundCallable boundMacro = (BoundCallable) tm;
final Macro unboundMacro = boundMacro.getUnboundCallable();
@@ -84,8 +81,7 @@
+ "Functions can only be called from expressions, like in ${f()}, ${x + f()} or ",
"<@someDirective someParam=f() />", ".");
}
- env.invoke(boundMacro, namedArgs, positionalArgs, bodyParameterNames,
- getNestedBlock());
+ env.invoke(boundMacro, namedArgs, positionalArgs, bodyParameterNames, getChildBuffer());
} else {
boolean isDirectiveModel = tm instanceof TemplateDirectiveModel;
if (isDirectiveModel || tm instanceof TemplateTransformModel) {
@@ -103,9 +99,9 @@
args = EmptyMap.instance;
}
if (isDirectiveModel) {
- env.visit(getNestedBlock(), (TemplateDirectiveModel) tm, args, bodyParameterNames);
+ env.visit(getChildBuffer(), (TemplateDirectiveModel) tm, args, bodyParameterNames);
} else {
- env.visitAndTransform(getNestedBlock(), (TemplateTransformModel) tm, args);
+ env.visitAndTransform(getChildBuffer(), (TemplateTransformModel) tm, args);
}
} else if (tm == null) {
throw InvalidReferenceException.getInstance(nameExp, env);
@@ -113,6 +109,7 @@
throw new NonUserDefinedDirectiveLikeException(nameExp, tm, env);
}
}
+ return null;
}
@Override
@@ -152,11 +149,11 @@
}
}
if (canonical) {
- if (getNestedBlock() == null) {
+ if (getChildCount() == 0) {
sb.append("/>");
} else {
sb.append('>');
- sb.append(getNestedBlock().getCanonicalForm());
+ sb.append(getChildrenCanonicalForm());
sb.append("</@");
if (!nameIsInParen
&& (nameExp instanceof Identifier
@@ -303,8 +300,7 @@
}
public boolean isNestedOutputCacheable() {
- if (getNestedBlock() == null) return true;
- return getNestedBlock().isOutputCacheable();
+ return isChildrenOutputCacheable();
}
/*
@@ -341,5 +337,10 @@
boolean isNestedBlockRepeater() {
return true;
}
+
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
}
diff --git a/src/main/java/freemarker/core/VisitNode.java b/src/main/java/freemarker/core/VisitNode.java
index b35e3f8..e116c4a 100644
--- a/src/main/java/freemarker/core/VisitNode.java
+++ b/src/main/java/freemarker/core/VisitNode.java
@@ -42,7 +42,7 @@
}
@Override
- void accept(Environment env) throws IOException, TemplateException {
+ TemplateElement[] accept(Environment env) throws IOException, TemplateException {
TemplateModel node = targetNode.eval(env);
if (!(node instanceof TemplateNodeModel)) {
throw new NonNodeException(targetNode, node, env);
@@ -69,6 +69,7 @@
}
}
env.invokeNodeHandlerFor((TemplateNodeModel) node, (TemplateSequenceModel) nss);
+ return null;
}
@Override
@@ -118,5 +119,10 @@
boolean isNestedBlockRepeater() {
return true;
}
+
+ @Override
+ boolean isShownInStackTrace() {
+ return true;
+ }
}
diff --git a/src/main/java/freemarker/core/_CoreAPI.java b/src/main/java/freemarker/core/_CoreAPI.java
index bb8d0f1..1562662 100644
--- a/src/main/java/freemarker/core/_CoreAPI.java
+++ b/src/main/java/freemarker/core/_CoreAPI.java
@@ -466,4 +466,12 @@
}
}
+ public static TemplateElement getParentElement(TemplateElement te) {
+ return te.getParentElement();
+ }
+
+ public static TemplateElement getChildElement(TemplateElement te, int index) {
+ return te.getChild(index);
+ }
+
}
diff --git a/src/main/java/freemarker/debug/impl/RmiDebuggerService.java b/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
index 8065830..25d1b2e 100644
--- a/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
+++ b/src/main/java/freemarker/debug/impl/RmiDebuggerService.java
@@ -39,6 +39,7 @@
import freemarker.core.DebugBreak;
import freemarker.core.Environment;
import freemarker.core.TemplateElement;
+import freemarker.core._CoreAPI;
import freemarker.debug.Breakpoint;
import freemarker.debug.DebuggerListener;
import freemarker.debug.EnvironmentSuspendedEvent;
@@ -188,7 +189,7 @@
if (te == null) {
return;
}
- TemplateElement parent = te.getParent();
+ TemplateElement parent = _CoreAPI.getParentElement(te);
DebugBreak db = new DebugBreak(te);
// TODO: Ensure there always is a parent by making sure
// that the root element in the template is always a MixedContent
@@ -285,13 +286,13 @@
db = (DebugBreak) te;
break;
}
- te = te.getParent();
+ te = _CoreAPI.getParentElement(te);
}
if (db == null) {
return;
}
- TemplateElement parent = db.getParent();
- parent.setChildAt(parent.getIndex(db), db.getChildAt(0));
+ TemplateElement parent = _CoreAPI.getParentElement(db);
+ parent.setChildAt(parent.getIndex(db), _CoreAPI.getChildElement(db, 0));
}
void removeBreakpoints(String templateName) {
@@ -334,9 +335,9 @@
private void removeDebugBreaks(TemplateElement te) {
int count = te.getChildCount();
for (int i = 0; i < count; ++i) {
- TemplateElement child = te.getChildAt(i);
+ TemplateElement child = _CoreAPI.getChildElement(te, i);
while (child instanceof DebugBreak) {
- TemplateElement dbchild = child.getChildAt(0);
+ TemplateElement dbchild = _CoreAPI.getChildElement(child, 0);
te.setChildAt(i, dbchild);
child = dbchild;
}
diff --git a/src/main/java/freemarker/template/SimpleSequence.java b/src/main/java/freemarker/template/SimpleSequence.java
index 2730d58..0cc9411 100644
--- a/src/main/java/freemarker/template/SimpleSequence.java
+++ b/src/main/java/freemarker/template/SimpleSequence.java
@@ -99,7 +99,6 @@
public SimpleSequence(int capacity) {
list = new ArrayList(capacity);
}
-
/**
* Constructs a simple sequence that will contain the elements
diff --git a/src/main/java/freemarker/template/Template.java b/src/main/java/freemarker/template/Template.java
index 1efab5d..da55d06 100644
--- a/src/main/java/freemarker/template/Template.java
+++ b/src/main/java/freemarker/template/Template.java
@@ -267,23 +267,28 @@
}
/**
- * Executes template, using the data-model provided, writing the generated output
- * to the supplied {@link Writer}.
+ * Executes template, using the data-model provided, writing the generated output to the supplied {@link Writer}.
*
- * <p>For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker
- * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead.
+ * <p>
+ * For finer control over the runtime environment setup, such as per-HTTP-request configuring of FreeMarker
+ * settings, you may need to use {@link #createProcessingEnvironment(Object, Writer)} instead.
*
- * @param dataModel the holder of the variables visible from the template (name-value pairs); usually a
- * {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables).
- * Can be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}.
- * You can also use an object that already implements {@link TemplateHashModel}; in that case it won't be
- * wrapped. If it's {@code null}, an empty data model is used.
- * @param out The {@link Writer} where the output of the template will go. Note that unless you have used
- * {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at the
- * when the template processing was finished. {@link Writer#close()} is not called.
+ * @param dataModel
+ * the holder of the variables visible from the template (name-value pairs); usually a
+ * {@code Map<String, Object>} or a JavaBean (where the JavaBean properties will be the variables). Can
+ * be any object that the {@link ObjectWrapper} in use turns into a {@link TemplateHashModel}. You can
+ * also use an object that already implements {@link TemplateHashModel}; in that case it won't be
+ * wrapped. If it's {@code null}, an empty data model is used.
+ * @param out
+ * The {@link Writer} where the output of the template will go. Note that unless you have used
+ * {@link Configuration#setAutoFlush(boolean)} to disable this, {@link Writer#flush()} will be called at
+ * the when the template processing was finished. {@link Writer#close()} is not called. Can't be
+ * {@code null}.
*
- * @throws TemplateException if an exception occurs during template processing
- * @throws IOException if an I/O exception occurs during writing to the writer.
+ * @throws TemplateException
+ * if an exception occurs during template processing
+ * @throws IOException
+ * if an I/O exception occurs during writing to the writer.
*/
public void process(Object dataModel, Writer out)
throws TemplateException, IOException {
diff --git a/src/main/javacc/FTL.jj b/src/main/javacc/FTL.jj
index 608b43d..2cffd4d 100644
--- a/src/main/javacc/FTL.jj
+++ b/src/main/javacc/FTL.jj
@@ -2357,37 +2357,37 @@
{
Token start, end, t;
Expression condition;
- TemplateElement block;
+ TemplateElements children;
IfBlock ifBlock;
ConditionalBlock cblock;
}
{
start = <IF>
condition = Expression()
- <DIRECTIVE_END>
- block = OptionalBlock()
+ end = <DIRECTIVE_END>
+ children = MixedContentElements()
{
- cblock = new ConditionalBlock(condition, block, ConditionalBlock.TYPE_IF);
- cblock.setLocation(template, start, block);
+ cblock = new ConditionalBlock(condition, children, ConditionalBlock.TYPE_IF);
+ cblock.setLocation(template, start, end, children);
ifBlock = new IfBlock(cblock);
}
(
t = <ELSE_IF>
condition = Expression()
- LooseDirectiveEnd()
- block = OptionalBlock()
+ end = LooseDirectiveEnd()
+ children = MixedContentElements()
{
- cblock = new ConditionalBlock(condition, block, ConditionalBlock.TYPE_ELSE_IF);
- cblock.setLocation(template, t, block);
+ cblock = new ConditionalBlock(condition, children, ConditionalBlock.TYPE_ELSE_IF);
+ cblock.setLocation(template, t, end, children);
ifBlock.addBlock(cblock);
}
)*
[
t = <ELSE>
- block = OptionalBlock()
+ children = MixedContentElements()
{
- cblock = new ConditionalBlock(null, block, ConditionalBlock.TYPE_ELSE);
- cblock.setLocation(template, t, block);
+ cblock = new ConditionalBlock(null, children, ConditionalBlock.TYPE_ELSE);
+ cblock.setLocation(template, t, t, children);
ifBlock.addBlock(cblock);
}
]
@@ -2401,12 +2401,12 @@
AttemptBlock Attempt() :
{
Token start, end;
- TemplateElement block;
+ TemplateElements children;
RecoveryBlock recoveryBlock;
}
{
start = <ATTEMPT>
- block = OptionalBlock()
+ children = MixedContentElements()
recoveryBlock = Recover()
(
end = <END_RECOVER>
@@ -2414,7 +2414,7 @@
end = <END_ATTEMPT>
)
{
- AttemptBlock result = new AttemptBlock(block, recoveryBlock);
+ AttemptBlock result = new AttemptBlock(children, recoveryBlock);
result.setLocation(template, start, end);
return result;
}
@@ -2423,14 +2423,14 @@
RecoveryBlock Recover() :
{
Token start;
- TemplateElement block;
+ TemplateElements children;
}
{
start = <RECOVER>
- block = OptionalBlock()
+ children = MixedContentElements()
{
- RecoveryBlock result = new RecoveryBlock(block);
- result.setLocation(template, start, block);
+ RecoveryBlock result = new RecoveryBlock(children);
+ result.setLocation(template, start, start, children);
return result;
}
}
@@ -2439,7 +2439,7 @@
{
Expression exp;
Token loopVar = null, start, end;
- TemplateElement mainBlock;
+ TemplateElements childrendBeforeElse;
ElseOfList elseOfList = null;
ParserIteratorBlockContext iterCtx;
}
@@ -2459,7 +2459,7 @@
}
}
- mainBlock = OptionalBlock()
+ childrendBeforeElse = MixedContentElements()
{
if (loopVar != null) {
breakableDirectiveNesting--;
@@ -2477,7 +2477,7 @@
end = <END_LIST>
{
- IteratorBlock list = new IteratorBlock(exp, loopVar != null ? loopVar.image : null, mainBlock, false);
+ IteratorBlock list = new IteratorBlock(exp, loopVar != null ? loopVar.image : null, childrendBeforeElse, false);
list.setLocation(template, start, end);
TemplateElement result;
@@ -2494,14 +2494,14 @@
ElseOfList ElseOfList() :
{
Token start;
- TemplateElement block;
+ TemplateElements children;
}
{
start = <ELSE>
- block = OptionalBlock()
+ children = MixedContentElements()
{
- ElseOfList result = new ElseOfList(block);
- result.setLocation(template, start, block);
+ ElseOfList result = new ElseOfList(children);
+ result.setLocation(template, start, start, children);
return result;
}
}
@@ -2510,7 +2510,7 @@
{
Expression exp;
Token loopVar, start, end;
- TemplateElement block;
+ TemplateElements children;
}
{
start = <FOREACH>
@@ -2525,14 +2525,14 @@
breakableDirectiveNesting++;
}
- block = OptionalBlock()
+ children = MixedContentElements()
end = <END_FOREACH>
{
breakableDirectiveNesting--;
popIteratorBlockContext();
- IteratorBlock result = new IteratorBlock(exp, loopVar.image, block, true);
+ IteratorBlock result = new IteratorBlock(exp, loopVar.image, children, true);
result.setLocation(template, start, end);
return result;
}
@@ -2541,7 +2541,7 @@
Items Items() :
{
Token loopVar, start, end;
- TemplateElement block;
+ TemplateElements children;
ParserIteratorBlockContext iterCtx;
}
{
@@ -2570,14 +2570,14 @@
breakableDirectiveNesting++;
}
- block = OptionalBlock()
+ children = MixedContentElements()
end = <END_ITEMS>
{
breakableDirectiveNesting--;
iterCtx.loopVarName = null;
- Items result = new Items(loopVar.image, block);
+ Items result = new Items(loopVar.image, children);
result.setLocation(template, start, end);
return result;
}
@@ -2586,7 +2586,7 @@
Sep Sep() :
{
Token loopVar, start, end = null;
- TemplateElement block;
+ TemplateElements children;
}
{
start = <SEP>
@@ -2597,17 +2597,17 @@
template, start);
}
}
- block = OptionalBlock()
+ children = MixedContentElements()
[
LOOKAHEAD(1)
end = <END_SEP>
]
{
- Sep result = new Sep(block);
+ Sep result = new Sep(children);
if (end != null) {
- result.setLocation(template, start, end); // Template, Token, Token
+ result.setLocation(template, start, end);
} else {
- result.setLocation(template, start, block); // Template, Token, TemplateObject
+ result.setLocation(template, start, start, children);
}
return result;
}
@@ -2832,7 +2832,7 @@
String varName;
ArrayList assignments = new ArrayList();
Assignment ass;
- TemplateElement block;
+ TemplateElements children;
}
{
(
@@ -2962,7 +2962,7 @@
}
]
<DIRECTIVE_END>
- block = OptionalBlock()
+ children = MixedContentElements()
(
end = <END_LOCAL>
{
@@ -2986,7 +2986,7 @@
)
{
BlockAssignment ba = new BlockAssignment(
- block, varName, scope, nsExp,
+ children, varName, scope, nsExp,
getMarkupOutputFormat());
ba.setLocation(template, start, end);
return ba;
@@ -3070,7 +3070,7 @@
Expression defValue = null;
List lastIteratorBlockContexts;
int lastBreakableDirectiveNesting;
- TemplateElement block;
+ TemplateElements children;
boolean isFunction = false, hasDefaults = false;
boolean isCatchAll = false;
String catchAll = null;
@@ -3146,16 +3146,16 @@
lastBreakableDirectiveNesting = 0; // Just to prevent uninitialized local variable error later
}
}
- block = OptionalBlock()
+ children = MixedContentElements()
(
end = <END_MACRO>
{
- if(isFunction) throw new ParseException("Expected function end tag here.", template, start);
+ if (isFunction) throw new ParseException("Expected function end tag here.", template, start);
}
|
end = <END_FUNCTION>
{
- if(!isFunction) throw new ParseException("Expected macro end tag here.", template, start);
+ if (!isFunction) throw new ParseException("Expected macro end tag here.", template, start);
}
)
{
@@ -3165,7 +3165,7 @@
}
inMacro = inFunction = false;
- UnboundCallable result = new UnboundCallable(name, argNames, args, catchAll, isFunction, block);
+ UnboundCallable result = new UnboundCallable(name, argNames, args, catchAll, isFunction, children);
result.setLocation(template, start, end);
template.addUnboundCallable(result);
return result;
@@ -3174,15 +3174,15 @@
CompressedBlock Compress() :
{
- TemplateElement block;
+ TemplateElements children;
Token start, end;
}
{
start = <COMPRESS>
- block = OptionalBlock()
+ children = MixedContentElements()
end = <END_COMPRESS>
{
- CompressedBlock cb = new CompressedBlock(block);
+ CompressedBlock cb = new CompressedBlock(children);
cb.setLocation(template, start, end);
return cb;
}
@@ -3194,7 +3194,7 @@
HashMap namedArgs = null;
ArrayList positionalArgs = null, bodyParameters = null;
Expression startTagNameExp;
- TemplateElement nestedBlock = null;
+ TemplateElements children;
Expression exp;
int pushedCtxCount = 0;
}
@@ -3226,7 +3226,7 @@
]
]
(
- end = <EMPTY_DIRECTIVE_END>
+ end = <EMPTY_DIRECTIVE_END> { children = TemplateElements.EMPTY; }
|
(
<DIRECTIVE_END> {
@@ -3253,7 +3253,7 @@
}
}
}
- nestedBlock = OptionalBlock()
+ children = MixedContentElements()
end = <UNIFIED_CALL_END>
{
for (int i = 0; i < pushedCtxCount; i++) {
@@ -3276,8 +3276,8 @@
)
{
TemplateElement result = (positionalArgs != null)
- ? new UnifiedCall(exp, positionalArgs, nestedBlock, bodyParameters)
- : new UnifiedCall(exp, namedArgs, nestedBlock, bodyParameters);
+ ? new UnifiedCall(exp, positionalArgs, children, bodyParameters)
+ : new UnifiedCall(exp, namedArgs, children, bodyParameters);
result.setLocation(template, start, end);
return result;
}
@@ -3310,9 +3310,9 @@
{
UnifiedCall result = null;
if (positionalArgs != null) {
- result = new UnifiedCall(new Identifier(macroName), positionalArgs, null, null);
+ result = new UnifiedCall(new Identifier(macroName), positionalArgs, TemplateElements.EMPTY, null);
} else {
- result = new UnifiedCall(new Identifier(macroName), namedArgs, null, null);
+ result = new UnifiedCall(new Identifier(macroName), namedArgs, TemplateElements.EMPTY, null);
}
result.legacySyntax = true;
result.setLocation(template, start, end);
@@ -3402,7 +3402,7 @@
{
Token start, end, argName;
Expression exp, argExp;
- TemplateElement content = null;
+ TemplateElements children = null;
HashMap args = null;
}
{
@@ -3423,12 +3423,12 @@
|
(
<DIRECTIVE_END>
- content = OptionalBlock()
+ children = MixedContentElements()
end = <END_TRANSFORM>
)
)
{
- TransformBlock result = new TransformBlock(exp, args, content);
+ TransformBlock result = new TransformBlock(exp, args, children);
result.setLocation(template, start, end);
return result;
}
@@ -3476,7 +3476,7 @@
Case Case() :
{
Expression exp;
- TemplateElement block;
+ TemplateElements children;
Token start;
}
{
@@ -3486,10 +3486,10 @@
|
start = <DEFAUL> { exp = null; }
)
- block = OptionalBlock()
+ children = MixedContentElements()
{
- Case result = new Case(exp, block);
- result.setLocation(template, start, block);
+ Case result = new Case(exp, children);
+ result.setLocation(template, start, start, children);
return result;
}
}
@@ -3498,7 +3498,7 @@
{
Token variable, start, end;
Expression escapeExpr;
- TemplateElement content;
+ TemplateElements children;
}
{
start = <ESCAPE>
@@ -3519,9 +3519,9 @@
EscapeBlock result = new EscapeBlock(variable.image, escapeExpr, escapedExpression(escapeExpr));
escapes.addFirst(result);
}
- content = OptionalBlock()
+ children = MixedContentElements()
{
- result.setContent(content);
+ result.setContent(children);
escapes.removeFirst();
}
end = <END_ESCAPE>
@@ -3534,7 +3534,7 @@
NoEscapeBlock NoEscape() :
{
Token start, end;
- TemplateElement content;
+ TemplateElements children;
}
{
start = <NOESCAPE>
@@ -3544,11 +3544,11 @@
}
Object escape = escapes.removeFirst();
}
- content = OptionalBlock()
+ children = MixedContentElements()
end = <END_NOESCAPE>
{
escapes.addFirst(escape);
- NoEscapeBlock result = new NoEscapeBlock(content);
+ NoEscapeBlock result = new NoEscapeBlock(children);
result.setLocation(template, start, end);
return result;
}
@@ -3558,7 +3558,7 @@
{
Token start, end;
Expression paramExp;
- TemplateElement content;
+ TemplateElements children;
OutputFormat lastOutputFormat;
}
{
@@ -3631,10 +3631,10 @@
throw new ParseException(e.getMessage(), template, start, e.getCause());
}
}
- content = OptionalBlock()
+ children = MixedContentElements()
end = <END_OUTPUTFORMAT>
{
- OutputFormatBlock result = new OutputFormatBlock(content, paramExp);
+ OutputFormatBlock result = new OutputFormatBlock(children, paramExp);
result.setLocation(template, start, end);
outputFormat = lastOutputFormat;
@@ -3646,7 +3646,7 @@
AutoEscBlock AutoEsc() :
{
Token start, end;
- TemplateElement content;
+ TemplateElements children;
int lastAutoEscapingPolicy;
}
{
@@ -3657,10 +3657,10 @@
autoEscapingPolicy = Configuration.ENABLE_IF_SUPPORTED_AUTO_ESCAPING_POLICY;
recalculateAutoEscapingField();
}
- content = OptionalBlock()
+ children = MixedContentElements()
end = <END_AUTOESC>
{
- AutoEscBlock result = new AutoEscBlock(content);
+ AutoEscBlock result = new AutoEscBlock(children);
result.setLocation(template, start, end);
autoEscapingPolicy = lastAutoEscapingPolicy;
@@ -3672,7 +3672,7 @@
NoAutoEscBlock NoAutoEsc() :
{
Token start, end;
- TemplateElement content;
+ TemplateElements children;
int lastAutoEscapingPolicy;
}
{
@@ -3682,10 +3682,10 @@
autoEscapingPolicy = Configuration.DISABLE_AUTO_ESCAPING_POLICY;
recalculateAutoEscapingField();
}
- content = OptionalBlock()
+ children = MixedContentElements()
end = <END_NOAUTOESC>
{
- NoAutoEscBlock result = new NoAutoEscBlock(content);
+ NoAutoEscBlock result = new NoAutoEscBlock(children);
result.setLocation(template, start, end);
autoEscapingPolicy = lastAutoEscapingPolicy;
@@ -3740,7 +3740,7 @@
}
{
// Note that this doesn't include elements like "else", "recover", etc., because those indicate the end
- // of the OptionalBlock() of "if", "attempt", etc.
+ // of the MixedContentElements of "if", "attempt", etc.
(
tp = If()
|
@@ -3838,7 +3838,7 @@
}
)+
{
- if (stripText && mixedContentNesting == 1) return TextBlock.EMPTY_BLOCK;
+ if (stripText && mixedContentNesting == 1) return null;
TextBlock result = new TextBlock(buf.toString(), false);
result.setLocation(template, start, t);
@@ -3873,10 +3873,11 @@
}
}
-MixedContent MixedContent() :
+TemplateElements MixedContentElements() :
{
- MixedContent mixedContent = new MixedContent();
- TemplateElement elem, begin = null;
+ TemplateElement[] childBuffer = null;
+ int childCount = 0;
+ TemplateElement elem;
mixedContentNesting++;
}
{
@@ -3892,16 +3893,25 @@
elem = FreemarkerDirective()
)
{
- if (begin == null) {
- begin = elem;
+ // Note: elem == null when it's was top-level PCData removed by stripText
+ if (elem != null) {
+ childCount++;
+ if (childBuffer == null) {
+ childBuffer = new TemplateElement[16];
+ } else if (childBuffer.length < childCount) {
+ TemplateElement[] newChildBuffer = new TemplateElement[childCount * 2];
+ for (int i = 0; i < childBuffer.length; i++) {
+ newChildBuffer[i] = childBuffer[i];
+ }
+ childBuffer = newChildBuffer;
+ }
+ childBuffer[childCount - 1] = elem;
}
- mixedContent.addElement(elem);
}
- )+
+ )*
{
mixedContentNesting--;
- mixedContent.setLocation(template, begin, elem);
- return mixedContent;
+ return childBuffer != null ? new TemplateElements(childBuffer, childCount) : TemplateElements.EMPTY;
}
}
@@ -3927,7 +3937,7 @@
if (begin == null) {
begin = elem;
}
- nodes.addElement(elem);
+ nodes.addChild(elem);
}
)+
{
@@ -3936,25 +3946,6 @@
}
}
-/**
- * A production for a block of optional content.
- * Returns an empty Text block if there is no
- * content.
- */
-TemplateElement OptionalBlock() :
-{
- TemplateElement tp = TextBlock.EMPTY_BLOCK;
-}
-{
- [
- LOOKAHEAD(1) // has no effect but to get rid of a spurious warning.
- tp = MixedContent()
- ]
- {
- return tp;
- }
-}
-
void HeaderElement() :
{
Token key;
@@ -4183,20 +4174,21 @@
*/
TemplateElement Root() :
{
- TemplateElement doc;
+ TemplateElements children;
}
{
[
LOOKAHEAD([<STATIC_TEXT_WS>](<TRIVIAL_FTL_HEADER>|<FTL_HEADER>))
HeaderElement()
]
- doc = OptionalBlock()
+ children = MixedContentElements()
<EOF>
{
- doc.setFieldsForRootElement();
- doc = doc.postParseCleanup(stripWhitespace);
+ TemplateElement root = children.asSingleElement();
+ root.setFieldsForRootElement();
+ root = root.postParseCleanup(stripWhitespace);
// The cleanup result is possibly an element from deeper:
- doc.setFieldsForRootElement();
- return doc;
+ root.setFieldsForRootElement();
+ return root;
}
}
diff --git a/src/manual/book.xml b/src/manual/book.xml
index a35f8d2..0b80c41 100644
--- a/src/manual/book.xml
+++ b/src/manual/book.xml
@@ -26200,6 +26200,12 @@
</listitem>
<listitem>
+ <para>Decrease the stack usage of template execution, which can
+ have importance if you have very very deeply nested
+ templates.</para>
+ </listitem>
+
+ <listitem>
<para>Bug fixed, only with
<literal>incompatible_improvements</literal> set to 2.3.24
(<link linkend="topic.defaultObjectWrapperIcI">see how
@@ -26287,6 +26293,24 @@
</listitem>
<listitem>
+ <para>Internal reworking to simplify the AST (the
+ <literal>TemplateElement</literal> structure). The API related
+ technically public API was marked as internal for a good while.
+ For those who still use that API, the visible change is that
+ <literal>TemplateElement</literal>-s now almost never has a
+ <literal>MixedContent</literal> parent, instead, the parent is
+ directly whatever element the child element indeed belongs under
+ when you look at the source code (like the enclosing
+ <literal>#list</literal> for example, no a
+ <literal>MixedContent</literal> whose parent is the
+ <literal>#list</literal>). Note that when you have moved
+ downwards, towards the child elements, these
+ <literal>MixedContent</literal> parents weren't visible either
+ either, so the tree traversal API was in fact inconsistent. Now
+ it's consistent.</para>
+ </listitem>
+
+ <listitem>
<para>The non-public AST API of
<literal>freemarker.core.StringLiteral</literal>-s has been
changed. In principle it doesn't mater as it isn't a public API,
diff --git a/src/test/java/freemarker/core/ASTPrinter.java b/src/test/java/freemarker/core/ASTPrinter.java
index 9d2c53d..68d6ba0 100644
--- a/src/test/java/freemarker/core/ASTPrinter.java
+++ b/src/test/java/freemarker/core/ASTPrinter.java
@@ -260,14 +260,21 @@
}
private static void validateAST(TemplateElement te) {
- int ln = te.getRegulatedChildCount();
- for (int i = 0; i < ln; i++) {
- TemplateElement child = te.getRegulatedChild(i);
- if (child.getParentElement() != te) {
+ int childCount = te.getChildCount();
+ for (int i = 0; i < childCount; i++) {
+ TemplateElement child = te.getChild(i);
+ TemplateElement parentElement = child.getParentElement();
+ // As MixedContent.accept does nothing but return its regulatedChildren, it's optimized out in the final
+ // AST tree. While it will be present as a child, the parent element also will have regularedChildren
+ // that contains the children of the MixedContent directly.
+ if (parentElement instanceof MixedContent && parentElement.getParentElement() != null) {
+ parentElement = parentElement.getParentElement();
+ }
+ if (parentElement != te) {
throw new InvalidASTException("Wrong parent node."
+ "\nNode: " + child.dump(false)
+ "\nExpected parent: " + te.dump(false)
- + "\nActual parent: " + child.getParentElement().dump(false));
+ + "\nActual parent: " + parentElement.dump(false));
}
if (child.getIndex() != i) {
throw new InvalidASTException("Wrong node index."
@@ -276,13 +283,37 @@
+ "\nActual index: " + child.getIndex());
}
}
- if (te instanceof MixedContent && te.getRegulatedChildCount() < 2) {
+ if (te instanceof MixedContent && te.getChildCount() < 2) {
throw new InvalidASTException("Mixed content with child count less than 2 should removed by optimizatoin, "
- + "but found one with " + te.getRegulatedChildCount() + " child(ren).");
+ + "but found one with " + te.getChildCount() + " child(ren).");
}
- if (te.getRegulatedChildCount() != 0 && te.getNestedBlock() != null) {
- throw new InvalidASTException("Can't have both nestedBlock and regulatedChildren."
- + "\nNode: " + te.dump(false));
+ TemplateElement[] regulatedChildren = te.getChildBuffer();
+ if (regulatedChildren != null) {
+ if (childCount == 0) {
+ throw new InvalidASTException(
+ "regularChildren must be null when regularChild is 0."
+ + "\nNode: " + te.dump(false));
+ }
+ for (int i = 0; i < te.getChildCount(); i++) {
+ if (regulatedChildren[i] == null) {
+ throw new InvalidASTException(
+ "regularChildren can't be null at index " + i
+ + "\nNode: " + te.dump(false));
+ }
+ }
+ for (int i = te.getChildCount(); i < regulatedChildren.length; i++) {
+ if (regulatedChildren[i] != null) {
+ throw new InvalidASTException(
+ "regularChildren can't be non-null at index " + i
+ + "\nNode: " + te.dump(false));
+ }
+ }
+ } else {
+ if (childCount != 0) {
+ throw new InvalidASTException(
+ "regularChildren mustn't be null when regularChild isn't 0."
+ + "\nNode: " + te.dump(false));
+ }
}
}
diff --git a/src/test/java/freemarker/core/ASTTest.java b/src/test/java/freemarker/core/ASTTest.java
index 6ba4378..a700334 100644
--- a/src/test/java/freemarker/core/ASTTest.java
+++ b/src/test/java/freemarker/core/ASTTest.java
@@ -58,6 +58,10 @@
public void testMixedContentSimplifications() throws Exception {
testAST("ast-mixedcontentsimplifications");
}
+
+ public void testMultipleIgnoredChildren() throws Exception {
+ testAST("ast-multipleignoredchildren");
+ }
private void testAST(String testName) throws FileNotFoundException, IOException {
final String templateName = testName + ".ftl";
diff --git a/src/test/resources/freemarker/core/ast-multipleignoredchildren.ast b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ast
new file mode 100644
index 0000000..798e81b
--- /dev/null
+++ b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ast
@@ -0,0 +1,12 @@
+#mixed_content // f.c.MixedContent
+ #text // f.c.TextBlock
+ - content: "a\n" // String
+ #text // f.c.TextBlock
+ - content: "b\n" // String
+ #text // f.c.TextBlock
+ - content: "c\n" // String
+ #text // f.c.TextBlock
+ - content: "d\n" // String
+ #if // f.c.ConditionalBlock
+ - condition: true // f.c.BooleanLiteral
+ - AST-node subtype: "0" // Integer
diff --git a/src/test/resources/freemarker/core/ast-multipleignoredchildren.ftl b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ftl
new file mode 100644
index 0000000..10b8d87
--- /dev/null
+++ b/src/test/resources/freemarker/core/ast-multipleignoredchildren.ftl
@@ -0,0 +1,15 @@
+a
+<#compress></#compress>
+b
+<#compress></#compress>
+<#compress></#compress>
+c
+<#compress></#compress>
+<#compress></#compress>
+<#compress></#compress>
+d
+<#if true>
+ <#compress></#compress>
+ <#compress></#compress>
+ <#compress></#compress>
+</#if>
\ No newline at end of file