/* * 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 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 { 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(); } public int getIndex(TemplateElement 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 Internal API - even internally, use {@link #getChild(int)} instead. */ @Deprecated public TemplateElement 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 Don't use in internal code either; use {@link #getParentElement()} there. */ @Deprecated public TemplateElement 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; } }