001: package org.apache.anakia;
002:
003: /*
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021:
022: import java.io.IOException;
023: import java.io.StringWriter;
024: import java.io.Writer;
025: import java.util.ArrayList;
026: import java.util.Collection;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.ListIterator;
030:
031: import org.jdom.Attribute;
032: import org.jdom.CDATA;
033: import org.jdom.Comment;
034: import org.jdom.DocType;
035: import org.jdom.Document;
036: import org.jdom.Element;
037: import org.jdom.EntityRef;
038: import org.jdom.ProcessingInstruction;
039: import org.jdom.Text;
040: import org.jdom.output.XMLOutputter;
041:
042: /**
043: * Provides a class for wrapping a list of JDOM objects primarily for use in template
044: * engines and other kinds of text transformation tools.
045: * It has a {@link #toString()} method that will output the XML serialized form of the
046: * nodes it contains - again focusing on template engine usage, as well as the
047: * {@link #selectNodes(String)} method that helps selecting a different set of nodes
048: * starting from the nodes in this list. The class also implements the {@link java.util.List}
049: * interface by simply delegating calls to the contained list (the {@link #subList(int, int)}
050: * method is implemented by delegating to the contained list and wrapping the returned
051: * sublist into a <code>NodeList</code>).
052: *
053: * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
054: * @version $Id: NodeList.java 524478 2007-03-31 20:51:49Z wglass $
055: */
056: public class NodeList implements List, Cloneable {
057: private static final AttributeXMLOutputter DEFAULT_OUTPUTTER = new AttributeXMLOutputter();
058:
059: /** The contained nodes */
060: private List nodes;
061:
062: /**
063: * Creates an empty node list.
064: */
065: public NodeList() {
066: nodes = new ArrayList();
067: }
068:
069: /**
070: * Creates a node list that holds a single {@link Document} node.
071: * @param document
072: */
073: public NodeList(Document document) {
074: this ((Object) document);
075: }
076:
077: /**
078: * Creates a node list that holds a single {@link Element} node.
079: * @param element
080: */
081: public NodeList(Element element) {
082: this ((Object) element);
083: }
084:
085: private NodeList(Object object) {
086: if (object == null) {
087: throw new IllegalArgumentException(
088: "Cannot construct NodeList with null.");
089: }
090: nodes = new ArrayList(1);
091: nodes.add(object);
092: }
093:
094: /**
095: * Creates a node list that holds a list of nodes.
096: * @param nodes the list of nodes this template should hold. The created
097: * template will copy the passed nodes list, so changes to the passed list
098: * will not affect the model.
099: */
100: public NodeList(List nodes) {
101: this (nodes, true);
102: }
103:
104: /**
105: * Creates a node list that holds a list of nodes.
106: * @param nodes the list of nodes this template should hold.
107: * @param copy if true, the created template will copy the passed nodes
108: * list, so changes to the passed list will not affect the model. If false,
109: * the model will reference the passed list and will sense changes in it,
110: * altough no operations on the list will be synchronized.
111: */
112: public NodeList(List nodes, boolean copy) {
113: if (nodes == null) {
114: throw new IllegalArgumentException(
115: "Cannot initialize NodeList with null list");
116: }
117: this .nodes = copy ? new ArrayList(nodes) : nodes;
118: }
119:
120: /**
121: * Retrieves the underlying list used to store the nodes. Note however, that
122: * you can fully use the underlying list through the <code>List</code> interface
123: * of this class itself. You would probably access the underlying list only for
124: * synchronization purposes.
125: * @return The internal node List.
126: */
127: public List getList() {
128: return nodes;
129: }
130:
131: /**
132: * This method returns the string resulting from concatenation of string
133: * representations of its nodes. Each node is rendered using its XML
134: * serialization format. This greatly simplifies creating XML-transformation
135: * templates, as to output a node contained in variable x as XML fragment,
136: * you simply write ${x} in the template (or whatever your template engine
137: * uses as its expression syntax).
138: * @return The Nodelist as printable object.
139: */
140: public String toString() {
141: if (nodes.isEmpty()) {
142: return "";
143: }
144:
145: StringWriter sw = new StringWriter(nodes.size() * 128);
146: try {
147: for (Iterator i = nodes.iterator(); i.hasNext();) {
148: Object node = i.next();
149: if (node instanceof Element) {
150: DEFAULT_OUTPUTTER.output((Element) node, sw);
151: } else if (node instanceof Attribute) {
152: DEFAULT_OUTPUTTER.output((Attribute) node, sw);
153: } else if (node instanceof Text) {
154: DEFAULT_OUTPUTTER.output((Text) node, sw);
155: } else if (node instanceof Document) {
156: DEFAULT_OUTPUTTER.output((Document) node, sw);
157: } else if (node instanceof ProcessingInstruction) {
158: DEFAULT_OUTPUTTER.output(
159: (ProcessingInstruction) node, sw);
160: } else if (node instanceof Comment) {
161: DEFAULT_OUTPUTTER.output((Comment) node, sw);
162: } else if (node instanceof CDATA) {
163: DEFAULT_OUTPUTTER.output((CDATA) node, sw);
164: } else if (node instanceof DocType) {
165: DEFAULT_OUTPUTTER.output((DocType) node, sw);
166: } else if (node instanceof EntityRef) {
167: DEFAULT_OUTPUTTER.output((EntityRef) node, sw);
168: } else {
169: throw new IllegalArgumentException(
170: "Cannot process a "
171: + (node == null ? "null node"
172: : "node of class "
173: + node.getClass()
174: .getName()));
175: }
176: }
177: } catch (IOException e) {
178: // Cannot happen as we work with a StringWriter in memory
179: throw new Error();
180: }
181: return sw.toString();
182: }
183:
184: /**
185: * Returns a NodeList that contains the same nodes as this node list.
186: * @return A clone of this list.
187: * @throws CloneNotSupportedException if the contained list's class does
188: * not have an accessible no-arg constructor.
189: */
190: public Object clone() throws CloneNotSupportedException {
191: NodeList clonedList = (NodeList) super .clone();
192: clonedList.cloneNodes();
193: return clonedList;
194: }
195:
196: private void cloneNodes() throws CloneNotSupportedException {
197: Class listClass = nodes.getClass();
198: try {
199: List clonedNodes = (List) listClass.newInstance();
200: clonedNodes.addAll(nodes);
201: nodes = clonedNodes;
202: } catch (IllegalAccessException e) {
203: throw new CloneNotSupportedException(
204: "Cannot clone NodeList since"
205: + " there is no accessible no-arg constructor on class "
206: + listClass.getName());
207: } catch (InstantiationException e) {
208: // Cannot happen as listClass represents a concrete, non-primitive,
209: // non-array, non-void class - there's an instance of it in "nodes"
210: // which proves these assumptions.
211: throw new Error();
212: }
213: }
214:
215: /**
216: * Returns the hash code of the contained list.
217: * @return The hashcode of the list.
218: */
219: public int hashCode() {
220: return nodes.hashCode();
221: }
222:
223: /**
224: * Tests for equality with another object.
225: * @param o the object to test for equality
226: * @return true if the other object is also a NodeList and their contained
227: * {@link List} objects evaluate as equals.
228: */
229: public boolean equals(Object o) {
230: return o instanceof NodeList ? ((NodeList) o).nodes
231: .equals(nodes) : false;
232: }
233:
234: /**
235: * Applies an XPath expression to the node list and returns the resulting
236: * node list. In order for this method to work, your application must have
237: * access to <a href="http://code.werken.com">werken.xpath</a> library
238: * classes. The implementation does cache the parsed format of XPath
239: * expressions in a weak hash map, keyed by the string representation of
240: * the XPath expression. As the string object passed as the argument is
241: * usually kept in the parsed template, this ensures that each XPath
242: * expression is parsed only once during the lifetime of the template that
243: * first invoked it.
244: * @param xpathString the XPath expression you wish to apply
245: * @return a NodeList representing the nodes that are the result of
246: * application of the XPath to the current node list. It can be empty.
247: */
248: public NodeList selectNodes(String xpathString) {
249: return new NodeList(XPathCache.getXPath(xpathString).applyTo(
250: nodes), false);
251: }
252:
253: // List methods implemented hereafter
254:
255: /**
256: * @see java.util.List#add(java.lang.Object)
257: */
258: public boolean add(Object o) {
259: return nodes.add(o);
260: }
261:
262: /**
263: * @see java.util.List#add(int, java.lang.Object)
264: */
265: public void add(int index, Object o) {
266: nodes.add(index, o);
267: }
268:
269: /**
270: * @see java.util.List#addAll(java.util.Collection)
271: */
272: public boolean addAll(Collection c) {
273: return nodes.addAll(c);
274: }
275:
276: /**
277: * @see java.util.List#addAll(int, java.util.Collection)
278: */
279: public boolean addAll(int index, Collection c) {
280: return nodes.addAll(index, c);
281: }
282:
283: /**
284: * @see java.util.List#clear()
285: */
286: public void clear() {
287: nodes.clear();
288: }
289:
290: /**
291: * @see java.util.List#contains(java.lang.Object)
292: */
293: public boolean contains(Object o) {
294: return nodes.contains(o);
295: }
296:
297: /**
298: * @see java.util.List#containsAll(java.util.Collection)
299: */
300: public boolean containsAll(Collection c) {
301: return nodes.containsAll(c);
302: }
303:
304: /**
305: * @see java.util.List#get(int)
306: */
307: public Object get(int index) {
308: return nodes.get(index);
309: }
310:
311: /**
312: * @see java.util.List#indexOf(java.lang.Object)
313: */
314: public int indexOf(Object o) {
315: return nodes.indexOf(o);
316: }
317:
318: /**
319: * @see java.util.List#isEmpty()
320: */
321: public boolean isEmpty() {
322: return nodes.isEmpty();
323: }
324:
325: /**
326: * @see java.util.List#iterator()
327: */
328: public Iterator iterator() {
329: return nodes.iterator();
330: }
331:
332: /**
333: * @see java.util.List#lastIndexOf(java.lang.Object)
334: */
335: public int lastIndexOf(Object o) {
336: return nodes.lastIndexOf(o);
337: }
338:
339: /**
340: * @see java.util.List#listIterator()
341: */
342: public ListIterator listIterator() {
343: return nodes.listIterator();
344: }
345:
346: /**
347: * @see java.util.List#listIterator(int)
348: */
349: public ListIterator listIterator(int index) {
350: return nodes.listIterator(index);
351: }
352:
353: /**
354: * @see java.util.List#remove(int)
355: */
356: public Object remove(int index) {
357: return nodes.remove(index);
358: }
359:
360: /**
361: * @see java.util.List#remove(java.lang.Object)
362: */
363: public boolean remove(Object o) {
364: return nodes.remove(o);
365: }
366:
367: /**
368: * @see java.util.List#removeAll(java.util.Collection)
369: */
370: public boolean removeAll(Collection c) {
371: return nodes.removeAll(c);
372: }
373:
374: /**
375: * @see java.util.List#retainAll(java.util.Collection)
376: */
377: public boolean retainAll(Collection c) {
378: return nodes.retainAll(c);
379: }
380:
381: /**
382: * @see java.util.List#set(int, java.lang.Object)
383: */
384: public Object set(int index, Object o) {
385: return nodes.set(index, o);
386: }
387:
388: /**
389: * @see java.util.List#size()
390: */
391: public int size() {
392: return nodes.size();
393: }
394:
395: /**
396: * @see java.util.List#subList(int, int)
397: */
398: public List subList(int fromIndex, int toIndex) {
399: return new NodeList(nodes.subList(fromIndex, toIndex));
400: }
401:
402: /**
403: * @see java.util.List#toArray()
404: */
405: public Object[] toArray() {
406: return nodes.toArray();
407: }
408:
409: /**
410: * @see java.util.List#toArray(java.lang.Object[])
411: */
412: public Object[] toArray(Object[] a) {
413: return nodes.toArray(a);
414: }
415:
416: /**
417: * A special subclass of XMLOutputter that will be used to output
418: * Attribute nodes. As a subclass of XMLOutputter it can use its protected
419: * method escapeAttributeEntities() to serialize the attribute
420: * appropriately.
421: */
422: private static final class AttributeXMLOutputter extends
423: XMLOutputter {
424: /**
425: * @param attribute
426: * @param out
427: * @throws IOException
428: */
429: public void output(Attribute attribute, Writer out)
430: throws IOException {
431: out.write(" ");
432: out.write(attribute.getQualifiedName());
433: out.write("=");
434:
435: out.write("\"");
436: out.write(escapeAttributeEntities(attribute.getValue()));
437: out.write("\"");
438: }
439: }
440: }
|