blob: af18136507460972ad31560e01b8c542ed39189d [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package freemarker.core;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import javax.swing.tree.TreeNode;
import freemarker.template.SimpleSequence;
import freemarker.template.TemplateException;
import freemarker.template.TemplateNodeModel;
import freemarker.template.TemplateSequenceModel;
/**
* <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.
*
* @deprecated This is an internal FreeMarker API with no backward compatibility guarantees, so you shouldn't depend on
* it.
*/
@Deprecated
abstract public class TemplateElement extends TemplateObject implements TreeNode {
private static final int INITIAL_REGULATED_CHILD_BUFFER_CAPACITY = 6;
private TemplateElement parent;
/**
* 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[] childBuffer;
/**
* 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;
/**
* 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
*
* @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 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.
*
* This final implementation calls {@link #dump(boolean) dump(false)}.
*
* @see #getCanonicalForm()
* @see #getNodeTypeSymbol()
*/
public final String getDescription() {
return dump(false);
}
/**
* This final implementation calls {@link #dump(boolean) dump(false)}.
*/
@Override
public final String getCanonicalForm() {
return dump(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 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!
*
* @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
public TemplateNodeModel getParentNode() {
// return parent;
return null;
}
public String getNodeNamespace() {
return null;
}
public String getNodeType() {
return "element";
}
public TemplateSequenceModel getChildNodes() {
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);
}
}
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.
public boolean isLeaf() {
return childCount == 0;
}
/**
* @deprecated Meaningless; simply returns if the node currently has any child nodes.
*/
@Deprecated
public boolean getAllowsChildren() {
return !isLeaf();
}
/**
* @deprecated Starting from 2.4, we won't use {@link TreeNode} API, as it requires Swing.
*/
@Deprecated
public int getIndex(TreeNode node) {
for (int i = 0; i < childCount; i++) {
if (childBuffer[i].equals(node)) {
return i;
}
}
return -1;
}
public int getChildCount() {
return childCount;
}
/**
* Note: For element with {@code #nestedBlock}, this will hide the {@code #nestedBlock} when that's a
* {@link MixedContent}.
*/
public Enumeration children() {
return childBuffer != null
? new _ArrayEnumeration(childBuffer, childCount)
: Collections.enumeration(Collections.EMPTY_LIST);
}
/**
* @deprecated This method will return {@link TemplateElement} starting from 2.4, as that doesn't require Swing;
* don't use it. Internally, use {@link #getChild(int)} instead.
*/
@Deprecated
public TreeNode getChildAt(int index) {
if (childCount == 0) {
throw new IndexOutOfBoundsException("Template element has no children");
}
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);
}
}
public void setChildAt(int index, TemplateElement element) {
if (index < childCount && index >= 0) {
childBuffer[index] = element;
element.index = index;
element.parent = this;
} else {
throw new IndexOutOfBoundsException("Index: " + index + ", Size: " + childCount);
}
}
/**
* The element whose child this element is, or {@code null} if this is the root node.
*
* @deprecated This method will return {@link TemplateElement} starting from 2.4, as that doesn't require Swing;
* don't use it. Don't use in internal code either; use {@link #getParentElement()} there.
*/
@Deprecated
public TreeNode getParent() {
return parent;
}
/**
* The element whose child this element is, or {@code null} if this is the root node.
*/
final TemplateElement getParentElement() {
return parent;
}
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];
childBuffer = lRegulatedChildBuffer;
} else if (lRegulatedChildCount == lRegulatedChildBuffer.length) {
setChildBufferCapacity(lRegulatedChildCount != 0 ? lRegulatedChildCount * 2 : 1);
lRegulatedChildBuffer = childBuffer;
}
// At this point: nestedElements == this.nestedElements, and has sufficient capacity.
for (int i = lRegulatedChildCount; i > index; i--) {
TemplateElement movedElement = lRegulatedChildBuffer[i - 1];
movedElement.index = i;
lRegulatedChildBuffer[i] = movedElement;
}
nestedElement.index = index;
nestedElement.parent = this;
lRegulatedChildBuffer[index] = nestedElement;
childCount = lRegulatedChildCount + 1;
}
final TemplateElement getChild(int index) {
return childBuffer[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;
}
/**
* This is a special case, because a root element is not contained in another element, so we couldn't set the
* private fields.
*/
final void setFieldsForRootElement() {
index = 0;
parent = null;
}
/**
* Walk the AST subtree rooted by this element, and do simplifications where possible, also removes superfluous
* whitespace.
*
* @param stripWhitespace
* whether to remove superfluous whitespace
*
* @return The element this element should be replaced with in the parent. If it's the same as this element, no
* actual replacement will happen. Note that adjusting the {@link #parent} and {@link #index} of the result
* is the duty of the caller, not of this method.
*/
TemplateElement postParseCleanup(boolean stripWhitespace) throws ParseException {
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);
childBuffer[i] = te;
te.parent = this;
te.index = 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 (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];
}
childBuffer = trimmedChildBuffer;
}
}
return this;
}
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
TemplateElement prevTerminalNode() {
TemplateElement prev = previousSibling();
if (prev != null) {
return prev.getLastLeaf();
} else if (parent != null) {
return parent.prevTerminalNode();
}
return null;
}
TemplateElement nextTerminalNode() {
TemplateElement next = nextSibling();
if (next != null) {
return next.getFirstLeaf();
} else if (parent != null) {
return parent.nextTerminalNode();
}
return null;
}
TemplateElement previousSibling() {
if (parent == null) {
return null;
}
return index > 0 ? parent.childBuffer[index - 1] : null;
}
TemplateElement nextSibling() {
if (parent == null) {
return null;
}
return index + 1 < parent.childCount ? parent.childBuffer[index + 1] : null;
}
private TemplateElement getFirstChild() {
return childCount == 0 ? null : childBuffer[0];
}
private TemplateElement getLastChild() {
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
te = te.getFirstChild();
}
return te;
}
private TemplateElement getLastLeaf() {
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
te = te.getLastChild();
}
return te;
}
/**
* Tells if executing this element has output that only depends on the template content and that has no side
* effects.
*/
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.
*/
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.
*/
boolean heedsTrailingWhitespace() {
return false;
}
}