001: /*
002: * Copyright (c) 2003 The Visigoth Software Society. All rights
003: * reserved.
004: *
005: * Redistribution and use in source and binary forms, with or without
006: * modification, are permitted provided that the following conditions
007: * are met:
008: *
009: * 1. Redistributions of source code must retain the above copyright
010: * notice, this list of conditions and the following disclaimer.
011: *
012: * 2. Redistributions in binary form must reproduce the above copyright
013: * notice, this list of conditions and the following disclaimer in
014: * the documentation and/or other materials provided with the
015: * distribution.
016: *
017: * 3. The end-user documentation included with the redistribution, if
018: * any, must include the following acknowledgement:
019: * "This product includes software developed by the
020: * Visigoth Software Society (http://www.visigoths.org/)."
021: * Alternately, this acknowledgement may appear in the software itself,
022: * if and wherever such third-party acknowledgements normally appear.
023: *
024: * 4. Neither the name "FreeMarker", "Visigoth", nor any of the names of the
025: * project contributors may be used to endorse or promote products derived
026: * from this software without prior written permission. For written
027: * permission, please contact visigoths@visigoths.org.
028: *
029: * 5. Products derived from this software may not be called "FreeMarker" or "Visigoth"
030: * nor may "FreeMarker" or "Visigoth" appear in their names
031: * without prior written permission of the Visigoth Software Society.
032: *
033: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
034: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
035: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
036: * DISCLAIMED. IN NO EVENT SHALL THE VISIGOTH SOFTWARE SOCIETY OR
037: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
038: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
039: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
040: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
041: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
042: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
043: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
044: * SUCH DAMAGE.
045: * ====================================================================
046: *
047: * This software consists of voluntary contributions made by many
048: * individuals on behalf of the Visigoth Software Society. For more
049: * information on the Visigoth Software Society, please see
050: * http://www.visigoths.org/
051: */
052:
053: package freemarker.core;
054:
055: import java.util.*;
056: import java.io.IOException;
057: import javax.swing.tree.TreeNode;
058: import freemarker.template.*;
059: import freemarker.template.utility.Collections12;
060:
061: /**
062: * Objects that represent elements in the compiled
063: * tree representation of the template necessarily
064: * descend from this abstract class.
065: */
066: abstract public class TemplateElement extends TemplateObject implements
067: TreeNode {
068:
069: TemplateElement parent;
070:
071: // Only one of nestedBlock and nestedElements can be non-null.
072:
073: TemplateElement nestedBlock;
074:
075: List nestedElements;
076:
077: /**
078: * Processes the contents of this <tt>TemplateElement</tt> and
079: * outputs the resulting text
080: *
081: * @param env The runtime environment
082: */
083: abstract void accept(Environment env) throws TemplateException,
084: IOException;
085:
086: abstract public String getDescription();
087:
088: // Methods to implement TemplateNodeModel
089:
090: public TemplateNodeModel getParentNode() {
091: // return parent;
092: return null;
093: }
094:
095: public String getNodeNamespace() {
096: return null;
097: }
098:
099: public String getNodeType() {
100: return "element";
101: }
102:
103: public TemplateSequenceModel getChildNodes() {
104: if (nestedElements != null) {
105: return new SimpleSequence(nestedElements);
106: }
107: SimpleSequence result = null;
108: if (nestedBlock != null) {
109: result.add(nestedBlock);
110: }
111: return result;
112: }
113:
114: public String getNodeName() {
115: String classname = this .getClass().getName();
116: int shortNameOffset = classname.lastIndexOf('.') + 1;
117: return classname.substring(shortNameOffset);
118: }
119:
120: // Methods so that we can implement the Swing TreeNode API.
121:
122: public boolean isLeaf() {
123: return nestedBlock == null
124: && (nestedElements == null || nestedElements.isEmpty());
125: }
126:
127: public boolean getAllowsChildren() {
128: return !isLeaf();
129: }
130:
131: public int getIndex(TreeNode node) {
132: if (nestedBlock instanceof MixedContent) {
133: return nestedBlock.getIndex(node);
134: }
135: if (nestedBlock != null) {
136: if (node == nestedBlock) {
137: return 0;
138: }
139: } else if (nestedElements != null) {
140: return nestedElements.indexOf(node);
141: }
142: return -1;
143: }
144:
145: public int getChildCount() {
146: if (nestedBlock instanceof MixedContent) {
147: return nestedBlock.getChildCount();
148: }
149: if (nestedBlock != null) {
150: return 1;
151: } else if (nestedElements != null) {
152: return nestedElements.size();
153: }
154: return 0;
155: }
156:
157: public Enumeration children() {
158: if (nestedBlock instanceof MixedContent) {
159: return nestedBlock.children();
160: }
161: if (nestedBlock != null) {
162: return Collections.enumeration(Collections12
163: .singletonList(nestedBlock));
164: } else if (nestedElements != null) {
165: return Collections.enumeration(nestedElements);
166: }
167: return Collections.enumeration(Collections.EMPTY_LIST);
168: }
169:
170: public TreeNode getChildAt(int index) {
171: if (nestedBlock instanceof MixedContent) {
172: return nestedBlock.getChildAt(index);
173: }
174: if (nestedBlock != null) {
175: if (index == 0) {
176: return nestedBlock;
177: }
178: throw new ArrayIndexOutOfBoundsException("invalid index");
179: } else if (nestedElements != null) {
180: return (TreeNode) nestedElements.get(index);
181: }
182: throw new ArrayIndexOutOfBoundsException(
183: "element has no children");
184: }
185:
186: public void setChildAt(int index, TemplateElement element) {
187: if (nestedBlock instanceof MixedContent) {
188: nestedBlock.setChildAt(index, element);
189: } else if (nestedBlock != null) {
190: if (index == 0) {
191: nestedBlock = element;
192: element.parent = this ;
193: } else {
194: throw new IndexOutOfBoundsException("invalid index");
195: }
196: } else if (nestedElements != null) {
197: nestedElements.set(index, element);
198: element.parent = this ;
199: } else {
200: throw new IndexOutOfBoundsException(
201: "element has no children");
202: }
203: }
204:
205: public TreeNode getParent() {
206: return parent;
207: }
208:
209: // Walk the tree and set the parent field in all the nested elements recursively.
210:
211: void setParentRecursively(TemplateElement parent) {
212: this .parent = parent;
213: int nestedSize = nestedElements == null ? 0 : nestedElements
214: .size();
215: for (int i = 0; i < nestedSize; i++) {
216: ((TemplateElement) nestedElements.get(i))
217: .setParentRecursively(this );
218: }
219: if (nestedBlock != null) {
220: nestedBlock.setParentRecursively(this );
221: }
222: }
223:
224: /**
225: * We walk the tree and do some cleanup
226: * @param stripWhitespace whether to clean up superfluous whitespace
227: */
228: TemplateElement postParseCleanup(boolean stripWhitespace)
229: throws ParseException {
230: if (nestedElements != null) {
231: for (int i = 0; i < nestedElements.size(); i++) {
232: TemplateElement te = (TemplateElement) nestedElements
233: .get(i);
234: te = te.postParseCleanup(stripWhitespace);
235: nestedElements.set(i, te);
236: te.parent = this ;
237: }
238: if (stripWhitespace) {
239: for (Iterator it = nestedElements.iterator(); it
240: .hasNext();) {
241: TemplateElement te = (TemplateElement) it.next();
242: if (te.isIgnorable()) {
243: it.remove();
244: }
245: }
246: }
247: if (nestedElements instanceof ArrayList) {
248: ((ArrayList) nestedElements).trimToSize();
249: }
250: }
251: if (nestedBlock != null) {
252: nestedBlock = nestedBlock.postParseCleanup(stripWhitespace);
253: if (nestedBlock.isIgnorable()) {
254: nestedBlock = null;
255: } else {
256: nestedBlock.parent = this ;
257: }
258: }
259: return this ;
260: }
261:
262: boolean isIgnorable() {
263: return false;
264: }
265:
266: // The following methods exist to support some fancier tree-walking
267: // and were introduced to support the whitespace cleanup feature in 2.2
268:
269: TemplateElement prevTerminalNode() {
270: TemplateElement prev = previousSibling();
271: if (prev != null) {
272: return prev.getLastLeaf();
273: } else if (parent != null) {
274: return parent.prevTerminalNode();
275: }
276: return null;
277: }
278:
279: TemplateElement nextTerminalNode() {
280: TemplateElement next = nextSibling();
281: if (next != null) {
282: return next.getFirstLeaf();
283: } else if (parent != null) {
284: return parent.nextTerminalNode();
285: }
286: return null;
287: }
288:
289: TemplateElement previousSibling() {
290: if (parent == null) {
291: return null;
292: }
293: List siblings = parent.nestedElements;
294: if (siblings == null) {
295: return null;
296: }
297: for (int i = siblings.size() - 1; i >= 0; i--) {
298: if (siblings.get(i) == this ) {
299: return (i > 0) ? (TemplateElement) siblings.get(i - 1)
300: : null;
301: }
302: }
303: return null;
304: }
305:
306: TemplateElement nextSibling() {
307: if (parent == null) {
308: return null;
309: }
310: List siblings = parent.nestedElements;
311: if (siblings == null) {
312: return null;
313: }
314: for (int i = 0; i < siblings.size(); i++) {
315: if (siblings.get(i) == this ) {
316: return (i + 1) < siblings.size() ? (TemplateElement) siblings
317: .get(i + 1)
318: : null;
319: }
320: }
321: return null;
322: }
323:
324: private TemplateElement getFirstChild() {
325: if (nestedBlock != null) {
326: return nestedBlock;
327: }
328: if (nestedElements != null && nestedElements.size() > 0) {
329: return (TemplateElement) nestedElements.get(0);
330: }
331: return null;
332: }
333:
334: private TemplateElement getLastChild() {
335: if (nestedBlock != null) {
336: return nestedBlock;
337: }
338: if (nestedElements != null && nestedElements.size() > 0) {
339: return (TemplateElement) nestedElements.get(nestedElements
340: .size() - 1);
341: }
342: return null;
343: }
344:
345: private TemplateElement getFirstLeaf() {
346: TemplateElement te = this ;
347: while (!te.isLeaf() && !(te instanceof Macro)
348: && !(te instanceof BlockAssignment)) {
349: // A macro or macro invocation is treated as a leaf here for special reasons
350: te = te.getFirstChild();
351: }
352: return te;
353: }
354:
355: private TemplateElement getLastLeaf() {
356: TemplateElement te = this ;
357: while (!te.isLeaf() && !(te instanceof Macro)
358: && !(te instanceof BlockAssignment)) {
359: // A macro or macro invocation is treated as a leaf here for special reasons
360: te = te.getLastChild();
361: }
362: return te;
363: }
364:
365: /**
366: * determines whether this element's presence on a line
367: * indicates that we should not strip opening whitespace
368: * in the post-parse whitespace gobbling step.
369: */
370: boolean heedsOpeningWhitespace() {
371: return false;
372: }
373:
374: /**
375: * determines whether this element's presence on a line
376: * indicates that we should not strip trailing whitespace
377: * in the post-parse whitespace gobbling step.
378: */
379: boolean heedsTrailingWhitespace() {
380: return false;
381: }
382: }
|