View Javadoc

1   package org.apache.velocity.anakia;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.io.IOException;
23  import java.io.StringWriter;
24  import java.io.Writer;
25  import java.util.ArrayList;
26  import java.util.Collection;
27  import java.util.Iterator;
28  import java.util.List;
29  import java.util.ListIterator;
30  
31  import org.jdom.Attribute;
32  import org.jdom.CDATA;
33  import org.jdom.Comment;
34  import org.jdom.DocType;
35  import org.jdom.Document;
36  import org.jdom.Element;
37  import org.jdom.EntityRef;
38  import org.jdom.ProcessingInstruction;
39  import org.jdom.Text;
40  import org.jdom.output.XMLOutputter;
41  
42  /**
43   * Provides a class for wrapping a list of JDOM objects primarily for use in template
44   * engines and other kinds of text transformation tools.
45   * It has a {@link #toString()} method that will output the XML serialized form of the
46   * nodes it contains - again focusing on template engine usage, as well as the
47   * {@link #selectNodes(String)} method that helps selecting a different set of nodes
48   * starting from the nodes in this list. The class also implements the {@link java.util.List}
49   * interface by simply delegating calls to the contained list (the {@link #subList(int, int)}
50   * method is implemented by delegating to the contained list and wrapping the returned
51   * sublist into a <code>NodeList</code>).
52   *
53   * @author <a href="mailto:szegedia@freemail.hu">Attila Szegedi</a>
54   * @version $Id: NodeList.java 463298 2006-10-12 16:10:32Z henning $
55   */
56  public class NodeList implements List, Cloneable
57  {
58      private static final AttributeXMLOutputter DEFAULT_OUTPUTTER =
59          new AttributeXMLOutputter();
60  
61      /** The contained nodes */
62      private List nodes;
63  
64      /**
65       * Creates an empty node list.
66       */
67      public NodeList()
68      {
69          nodes = new ArrayList();
70      }
71  
72      /**
73       * Creates a node list that holds a single {@link Document} node.
74       * @param document
75       */
76      public NodeList(Document document)
77      {
78          this((Object)document);
79      }
80  
81      /**
82       * Creates a node list that holds a single {@link Element} node.
83       * @param element
84       */
85      public NodeList(Element element)
86      {
87          this((Object)element);
88      }
89  
90      private NodeList(Object object)
91      {
92          if(object == null)
93          {
94              throw new IllegalArgumentException(
95                  "Cannot construct NodeList with null.");
96          }
97          nodes = new ArrayList(1);
98          nodes.add(object);
99      }
100 
101     /**
102      * Creates a node list that holds a list of nodes.
103      * @param nodes the list of nodes this template should hold. The created
104      * template will copy the passed nodes list, so changes to the passed list
105      * will not affect the model.
106      */
107     public NodeList(List nodes)
108     {
109         this(nodes, true);
110     }
111 
112     /**
113      * Creates a node list that holds a list of nodes.
114      * @param nodes the list of nodes this template should hold.
115      * @param copy if true, the created template will copy the passed nodes
116      * list, so changes to the passed list will not affect the model. If false,
117      * the model will reference the passed list and will sense changes in it,
118      * altough no operations on the list will be synchronized.
119      */
120     public NodeList(List nodes, boolean copy)
121     {
122         if(nodes == null)
123         {
124             throw new IllegalArgumentException(
125                 "Cannot initialize NodeList with null list");
126         }
127         this.nodes = copy ? new ArrayList(nodes) : nodes;
128     }
129 
130     /**
131      * Retrieves the underlying list used to store the nodes. Note however, that
132      * you can fully use the underlying list through the <code>List</code> interface
133      * of this class itself. You would probably access the underlying list only for
134      * synchronization purposes.
135      * @return The internal node List.
136      */
137     public List getList()
138     {
139         return nodes;
140     }
141 
142     /**
143      * This method returns the string resulting from concatenation of string
144      * representations of its nodes. Each node is rendered using its XML
145      * serialization format. This greatly simplifies creating XML-transformation
146      * templates, as to output a node contained in variable x as XML fragment,
147      * you simply write ${x} in the template (or whatever your template engine
148      * uses as its expression syntax).
149      * @return The Nodelist as printable object.
150      */
151     public String toString()
152     {
153         if(nodes.isEmpty())
154         {
155             return "";
156         }
157 
158         StringWriter sw = new StringWriter(nodes.size() * 128);
159         try
160         {
161             for(Iterator i = nodes.iterator(); i.hasNext();)
162             {
163                 Object node = i.next();
164                 if(node instanceof Element)
165                 {
166                     DEFAULT_OUTPUTTER.output((Element)node, sw);
167                 }
168                 else if(node instanceof Attribute)
169                 {
170                     DEFAULT_OUTPUTTER.output((Attribute)node, sw);
171                 }
172                 else if(node instanceof Text)
173                 {
174                     DEFAULT_OUTPUTTER.output((Text)node, sw);
175                 }
176                 else if(node instanceof Document)
177                 {
178                     DEFAULT_OUTPUTTER.output((Document)node, sw);
179                 }
180                 else if(node instanceof ProcessingInstruction)
181                 {
182                     DEFAULT_OUTPUTTER.output((ProcessingInstruction)node, sw);
183                 }
184                 else if(node instanceof Comment)
185                 {
186                     DEFAULT_OUTPUTTER.output((Comment)node, sw);
187                 }
188                 else if(node instanceof CDATA)
189                 {
190                     DEFAULT_OUTPUTTER.output((CDATA)node, sw);
191                 }
192                 else if(node instanceof DocType)
193                 {
194                     DEFAULT_OUTPUTTER.output((DocType)node, sw);
195                 }
196                 else if(node instanceof EntityRef)
197                 {
198                     DEFAULT_OUTPUTTER.output((EntityRef)node, sw);
199                 }
200                 else
201                 {
202                     throw new IllegalArgumentException(
203                         "Cannot process a " +
204                         (node == null
205                          ? "null node"
206                          : "node of class " + node.getClass().getName()));
207                 }
208             }
209         }
210         catch(IOException e)
211         {
212             // Cannot happen as we work with a StringWriter in memory
213             throw new Error();
214         }
215         return sw.toString();
216     }
217 
218     /**
219      * Returns a NodeList that contains the same nodes as this node list.
220      * @return A clone of this list.
221      * @throws CloneNotSupportedException if the contained list's class does
222      * not have an accessible no-arg constructor.
223      */
224     public Object clone()
225         throws CloneNotSupportedException
226     {
227         NodeList clonedList = (NodeList)super.clone();
228         clonedList.cloneNodes();
229         return clonedList;
230     }
231 
232     private void cloneNodes()
233         throws CloneNotSupportedException
234     {
235         Class listClass = nodes.getClass();
236         try
237         {
238             List clonedNodes = (List)listClass.newInstance();
239             clonedNodes.addAll(nodes);
240             nodes = clonedNodes;
241         }
242         catch(IllegalAccessException e)
243         {
244             throw new CloneNotSupportedException("Cannot clone NodeList since"
245             + " there is no accessible no-arg constructor on class "
246             + listClass.getName());
247         }
248         catch(InstantiationException e)
249         {
250             // Cannot happen as listClass represents a concrete, non-primitive,
251             // non-array, non-void class - there's an instance of it in "nodes"
252             // which proves these assumptions.
253             throw new Error();
254         }
255     }
256 
257     /**
258      * Returns the hash code of the contained list.
259      * @return The hashcode of the list.
260      */
261     public int hashCode()
262     {
263         return nodes.hashCode();
264     }
265 
266     /**
267      * Tests for equality with another object.
268      * @param o the object to test for equality
269      * @return true if the other object is also a NodeList and their contained
270      * {@link List} objects evaluate as equals.
271      */
272     public boolean equals(Object o)
273     {
274         return o instanceof NodeList
275             ? ((NodeList)o).nodes.equals(nodes)
276             : false;
277     }
278 
279     /**
280      * Applies an XPath expression to the node list and returns the resulting
281      * node list. In order for this method to work, your application must have
282      * access to <a href="http://code.werken.com">werken.xpath</a> library
283      * classes. The implementation does cache the parsed format of XPath
284      * expressions in a weak hash map, keyed by the string representation of
285      * the XPath expression. As the string object passed as the argument is
286      * usually kept in the parsed template, this ensures that each XPath
287      * expression is parsed only once during the lifetime of the template that
288      * first invoked it.
289      * @param xpathString the XPath expression you wish to apply
290      * @return a NodeList representing the nodes that are the result of
291      * application of the XPath to the current node list. It can be empty.
292      */
293     public NodeList selectNodes(String xpathString)
294     {
295         return new NodeList(XPathCache.getXPath(xpathString).applyTo(nodes), false);
296     }
297 
298 // List methods implemented hereafter
299 
300     /**
301      * @see java.util.List#add(java.lang.Object)
302      */
303     public boolean add(Object o)
304     {
305         return nodes.add(o);
306     }
307 
308     /**
309      * @see java.util.List#add(int, java.lang.Object)
310      */
311     public void add(int index, Object o)
312     {
313         nodes.add(index, o);
314     }
315 
316     /**
317      * @see java.util.List#addAll(java.util.Collection)
318      */
319     public boolean addAll(Collection c)
320     {
321         return nodes.addAll(c);
322     }
323 
324     /**
325      * @see java.util.List#addAll(int, java.util.Collection)
326      */
327     public boolean addAll(int index, Collection c)
328     {
329         return nodes.addAll(index, c);
330     }
331 
332     /**
333      * @see java.util.List#clear()
334      */
335     public void clear()
336     {
337         nodes.clear();
338     }
339 
340     /**
341      * @see java.util.List#contains(java.lang.Object)
342      */
343     public boolean contains(Object o)
344     {
345         return nodes.contains(o);
346     }
347 
348     /**
349      * @see java.util.List#containsAll(java.util.Collection)
350      */
351     public boolean containsAll(Collection c)
352     {
353         return nodes.containsAll(c);
354     }
355 
356     /**
357      * @see java.util.List#get(int)
358      */
359     public Object get(int index)
360     {
361         return nodes.get(index);
362     }
363 
364     /**
365      * @see java.util.List#indexOf(java.lang.Object)
366      */
367     public int indexOf(Object o)
368     {
369         return nodes.indexOf(o);
370     }
371 
372     /**
373      * @see java.util.List#isEmpty()
374      */
375     public boolean isEmpty()
376     {
377         return nodes.isEmpty();
378     }
379 
380     /**
381      * @see java.util.List#iterator()
382      */
383     public Iterator iterator()
384     {
385         return nodes.iterator();
386     }
387 
388     /**
389      * @see java.util.List#lastIndexOf(java.lang.Object)
390      */
391     public int lastIndexOf(Object o)
392     {
393         return nodes.lastIndexOf(o);
394     }
395 
396     /**
397      * @see java.util.List#listIterator()
398      */
399     public ListIterator listIterator()
400     {
401         return nodes.listIterator();
402     }
403 
404     /**
405      * @see java.util.List#listIterator(int)
406      */
407     public ListIterator listIterator(int index)
408     {
409         return nodes.listIterator(index);
410     }
411 
412     /**
413      * @see java.util.List#remove(int)
414      */
415     public Object remove(int index)
416     {
417         return nodes.remove(index);
418     }
419 
420     /**
421      * @see java.util.List#remove(java.lang.Object)
422      */
423     public boolean remove(Object o)
424     {
425         return nodes.remove(o);
426     }
427 
428     /**
429      * @see java.util.List#removeAll(java.util.Collection)
430      */
431     public boolean removeAll(Collection c)
432     {
433         return nodes.removeAll(c);
434     }
435 
436     /**
437      * @see java.util.List#retainAll(java.util.Collection)
438      */
439     public boolean retainAll(Collection c)
440     {
441         return nodes.retainAll(c);
442     }
443 
444     /**
445      * @see java.util.List#set(int, java.lang.Object)
446      */
447     public Object set(int index, Object o)
448     {
449         return nodes.set(index, o);
450     }
451 
452     /**
453      * @see java.util.List#size()
454      */
455     public int size()
456     {
457         return nodes.size();
458     }
459 
460     /**
461      * @see java.util.List#subList(int, int)
462      */
463     public List subList(int fromIndex, int toIndex)
464     {
465         return new NodeList(nodes.subList(fromIndex, toIndex));
466     }
467 
468     /**
469      * @see java.util.List#toArray()
470      */
471     public Object[] toArray()
472     {
473         return nodes.toArray();
474     }
475 
476     /**
477      * @see java.util.List#toArray(java.lang.Object[])
478      */
479     public Object[] toArray(Object[] a)
480     {
481         return nodes.toArray(a);
482     }
483 
484     /**
485      * A special subclass of XMLOutputter that will be used to output
486      * Attribute nodes. As a subclass of XMLOutputter it can use its protected
487      * method escapeAttributeEntities() to serialize the attribute
488      * appropriately.
489      */
490     private static final class AttributeXMLOutputter extends XMLOutputter
491     {
492         /**
493          * @param attribute
494          * @param out
495          * @throws IOException
496          */
497         public void output(Attribute attribute, Writer out)
498             throws IOException
499         {
500             out.write(" ");
501             out.write(attribute.getQualifiedName());
502             out.write("=");
503 
504             out.write("\"");
505             out.write(escapeAttributeEntities(attribute.getValue()));
506             out.write("\"");
507         }
508     }
509 }