KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > enhydra > xml > lazydom > html > HTMLCollectionImpl


1 /*
2  * The Apache Software License, Version 1.1
3  *
4  *
5  * Copyright (c) 1999,2000 The Apache Software Foundation. All rights
6  * reserved.
7  *
8  * Redistribution and use in source and binary forms, with or without
9  * modification, are permitted provided that the following conditions
10  * are met:
11  *
12  * 1. Redistributions of source code must retain the above copyright
13  * notice, this list of conditions and the following disclaimer.
14  *
15  * 2. Redistributions in binary form must reproduce the above copyright
16  * notice, this list of conditions and the following disclaimer in
17  * the documentation and/or other materials provided with the
18  * distribution.
19  *
20  * 3. The end-user documentation included with the redistribution,
21  * if any, must include the following acknowledgment:
22  * "This product includes software developed by the
23  * Apache Software Foundation (http://www.apache.org/)."
24  * Alternately, this acknowledgment may appear in the software itself,
25  * if and wherever such third-party acknowledgments normally appear.
26  *
27  * 4. The names "Xerces" and "Apache Software Foundation" must
28  * not be used to endorse or promote products derived from this
29  * software without prior written permission. For written
30  * permission, please contact apache@apache.org.
31  *
32  * 5. Products derived from this software may not be called "Apache",
33  * nor may "Apache" appear in their name, without prior written
34  * permission of the Apache Software Foundation.
35  *
36  * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
37  * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
38  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
39  * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
40  * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
41  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
42  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
43  * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
44  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
45  * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
46  * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
47  * SUCH DAMAGE.
48  * ====================================================================
49  *
50  * This software consists of voluntary contributions made by many
51  * individuals on behalf of the Apache Software Foundation and was
52  * originally based on software copyright (c) 1999, International
53  * Business Machines, Inc., http://www.apache.org. For more
54  * information on the Apache Software Foundation, please see
55  * <http://www.apache.org/>.
56  */

57 package org.enhydra.xml.lazydom.html;
58 import org.w3c.dom.Element JavaDoc;
59 import org.w3c.dom.Node JavaDoc;
60 import org.w3c.dom.html.HTMLAnchorElement;
61 import org.w3c.dom.html.HTMLAppletElement;
62 import org.w3c.dom.html.HTMLAreaElement;
63 import org.w3c.dom.html.HTMLCollection;
64 import org.w3c.dom.html.HTMLElement;
65 import org.w3c.dom.html.HTMLFormElement;
66 import org.w3c.dom.html.HTMLImageElement;
67 import org.w3c.dom.html.HTMLObjectElement;
68 import org.w3c.dom.html.HTMLOptionElement;
69 import org.w3c.dom.html.HTMLTableCellElement;
70 import org.w3c.dom.html.HTMLTableRowElement;
71 import org.w3c.dom.html.HTMLTableSectionElement;
72
73
74 /**
75  * Implements {@link org.w3c.dom.html.HTMLCollection} to traverse any named
76  * elements on a {@link org.w3c.dom.html.HTMLDocument}. The elements type to
77  * look for is identified in the constructor by code. This collection is not
78  * optimized for traversing large trees.
79  * <p>
80  * The collection has to meet two requirements: it has to be live, and it has
81  * to traverse depth first and always return results in that order. As such,
82  * using an object container (such as {@link java.util.Vector}) is expensive on
83  * insert/remove operations. Instead, the collection has been implemented using
84  * three traversing functions. As a result, operations on large documents will
85  * result in traversal of the entire document tree and consume a considerable
86  * amount of time.
87  * <p>
88  * Note that synchronization on the traversed document cannot be achieved.
89  * The document itself cannot be locked, and locking each traversed node is
90  * likely to lead to a dead lock condition. Therefore, there is a chance of the
91  * document being changed as results are fetched; in all likelihood, the results
92  * might be out dated, but not erroneous.
93  *
94  *
95  * @version $Revision: 1.2 $ $Date: 2005/01/26 08:29:24 $
96  * @author <a HREF="mailto:arkin@exoffice.com">Assaf Arkin</a>
97  * @see org.w3c.dom.html.HTMLCollection
98  */

99 class HTMLCollectionImpl
100     implements HTMLCollection
101 {
102     
103
104     /**
105      * Request collection of all anchors in document: &lt;A&gt; elements that
106      * have a <code>name</code> attribute.
107      */

108     static final short ANCHOR = 1;
109     
110     
111     /**
112      * Request collection of all forms in document: &lt;FORM&gt; elements.
113      */

114     static final short FORM = 2;
115     
116     
117     /**
118      * Request collection of all images in document: &lt;IMAGE&gt; elements.
119      */

120     static final short IMAGE = 3;
121     
122     
123     /**
124      * Request collection of all Applets in document: &lt;APPLET&gt; and
125      * &lt;OBJECT&gt; elements (&lt;OBJECT&gt; must contain an Applet).
126      */

127     static final short APPLET = 4;
128     
129     
130     /**
131      * Request collection of all links in document: &lt;A&gt; and &lt;AREA&gt;
132      * elements (must have a <code>href</code> attribute).
133      */

134     static final short LINK = 5;
135     
136     
137     /**
138      * Request collection of all options in selection: &lt;OPTION&gt; elments in
139      * &lt;SELECT&gt; or &lt;OPTGROUP&gt;.
140      */

141     static final short OPTION = 6;
142     
143     
144     /**
145      * Request collection of all rows in table: &lt;TR&gt; elements in table or
146      * table section.
147      */

148     static final short ROW = 7;
149
150     
151     /**
152      * Request collection of all form elements: &lt;INPUT&gt;, &lt;BUTTON&gt;,
153      * &lt;SELECT&gt;, &lt;TEXT&gt; and &lt;TEXTAREA&gt; elements inside form
154      * &lt;FORM&gt;.
155      */

156     static final short ELEMENT = 8;
157     
158     
159     /**
160      * Request collection of all areas in map: &lt;AREA&gt; element in &lt;MAP&gt;
161      * (non recursive).
162      */

163     static final short AREA = -1;
164     
165
166     /**
167      * Request collection of all table bodies in table: &lt;TBODY&gt; element in
168      * table &lt;TABLE&gt; (non recursive).
169      */

170     static final short TBODY = -2;
171
172     
173     /**
174      * Request collection of all cells in row: &lt;TD&gt; elements in &lt;TR&gt;
175      * (non recursive).
176      */

177     static final short CELL = -3;
178
179     
180     /**
181      * Indicates what this collection is looking for. Holds one of the enumerated
182      * values and used by {@link #collectionMatch}. Set by the constructor and
183      * determine the collection's use for its life time.
184      */

185     private short _lookingFor;
186     
187     
188     /**
189      * This is the top level element underneath which the collection exists.
190      */

191     private Element JavaDoc _topLevel;
192
193
194     /**
195      * Construct a new collection that retrieves element of the specific type
196      * (<code>lookingFor</code>) from the specific document portion
197      * (<code>topLevel</code>).
198      *
199      * @param topLevel The element underneath which the collection exists
200      * @param lookingFor Code indicating what elements to look for
201      */

202     HTMLCollectionImpl( HTMLElement topLevel, short lookingFor )
203     {
204         if ( topLevel == null )
205             throw new NullPointerException JavaDoc( "HTM011 Argument 'topLevel' is null." );
206         _topLevel = topLevel;
207        _lookingFor = lookingFor;
208     }
209   
210   
211     /**
212      * Returns the length of the collection. This method might traverse the
213      * entire document tree.
214      *
215      * @return Length of the collection
216      */

217     public final int getLength()
218     {
219         // Call recursive function on top-level element.
220
return getLength( _topLevel );
221     }
222
223
224     /**
225      * Retrieves the indexed node from the collection. Nodes are numbered in
226      * tree order - depth-first traversal order. This method might traverse
227      * the entire document tree.
228      *
229      * @param index The index of the node to return
230      * @return The specified node or null if no such node found
231      */

232     public final Node JavaDoc item( int index )
233     {
234         if ( index < 0 )
235             throw new IllegalArgumentException JavaDoc( "HTM012 Argument 'index' is negative." );
236         // Call recursive function on top-level element.
237
return item( _topLevel, new CollectionIndex( index ) );
238     }
239     
240     
241     /**
242      * Retrieves the named node from the collection. The name is matched case
243      * sensitive against the <TT>id</TT> attribute of each element in the
244      * collection, returning the first match. The tree is traversed in
245      * depth-first order. This method might traverse the entire document tree.
246      *
247      * @param name The name of the node to return
248      * @return The specified node or null if no such node found
249      */

250     public final Node JavaDoc namedItem( String JavaDoc name )
251     {
252         if ( name == null )
253             throw new NullPointerException JavaDoc( "HTM013 Argument 'name' is null." );
254         // Call recursive function on top-level element.
255
return namedItem( _topLevel, name );
256     }
257     
258     
259     /**
260      * Recursive function returns the number of elements of a particular type
261      * that exist under the top level element. This is a recursive function
262      * and the top level element is passed along.
263      *
264      * @param topLevel Top level element from which to scan
265      * @return Number of elements
266      */

267     private int getLength( Element JavaDoc topLevel )
268     {
269         int length;
270         Node JavaDoc node;
271     
272         synchronized ( topLevel )
273         {
274             // Always count from zero and traverse all the childs of the
275
// current element in the order they appear.
276
length = 0;
277             node = topLevel.getFirstChild();
278             while ( node != null )
279             {
280                 // If a particular node is an element (could be HTML or XML),
281
// do two things: if it's the one we're looking for, count
282
// another matched element; at any rate, traverse it's
283
// children as well.
284
if ( node instanceof Element JavaDoc )
285                 {
286                     if ( collectionMatch( (Element JavaDoc) node, null ) )
287                         ++ length;
288                     else if ( recurse() )
289                         length += getLength( (Element JavaDoc) node );
290                 }
291                 node = node.getNextSibling();
292             }
293         }
294         return length;
295     }
296     
297         
298     /**
299      * Recursive function returns the numbered element of a particular type
300      * that exist under the top level element. This is a recursive function
301      * and the top level element is passed along.
302      * <p>
303      * Note that this function must call itself with an index and get back both
304      * the element (if one was found) and the new index which is decremeneted
305      * for any like element found. Since integers are only passed by value,
306      * this function makes use of a separate class ({@link CollectionIndex})
307      * to hold that index.
308      *
309      * @param topLevel Top level element from which to scan
310      * @param index The index of the item to retreive
311      * @return Number of elements
312      * @see CollectionIndex
313      */

314     private Node JavaDoc item( Element JavaDoc topLevel, CollectionIndex index )
315     {
316         Node JavaDoc node;
317         Node JavaDoc result;
318
319         synchronized ( topLevel )
320         {
321             // Traverse all the childs of the current element in the order
322
// they appear. Count from the index backwards until you reach
323
// matching element with an index of zero. Return that element.
324
node = topLevel.getFirstChild();
325             while ( node != null )
326             {
327                 // If a particular node is an element (could be HTML or XML),
328
// do two things: if it's the one we're looking for, decrease
329
// the index and if zero, return this node; at any rate,
330
// traverse it's children as well.
331
if ( node instanceof Element JavaDoc )
332                 {
333                     if ( collectionMatch( (Element JavaDoc) node, null ) )
334                     {
335                         if ( index.isZero() )
336                             return node;
337                         index.decrement();
338                     } else if ( recurse() )
339                     {
340                         result = item( (Element JavaDoc) node, index );
341                         if ( result != null )
342                             return result;
343                     }
344                 }
345                 node = node.getNextSibling();
346             }
347         }
348         return null;
349     }
350     
351     
352     /**
353      * Recursive function returns an element of a particular type with the
354      * specified name (<TT>id</TT> attribute).
355      *
356      * @param topLevel Top level element from which to scan
357      * @param name The named element to look for
358      * @return The first named element found
359      */

360     private Node JavaDoc namedItem( Element JavaDoc topLevel, String JavaDoc name )
361     {
362         Node JavaDoc node;
363         Node JavaDoc result;
364
365         synchronized ( topLevel )
366         {
367             // Traverse all the childs of the current element in the order
368
// they appear.
369
node = topLevel.getFirstChild();
370             while ( node != null )
371             {
372                 // If a particular node is an element (could be HTML or XML),
373
// do two things: if it's the one we're looking for, and the
374
// name (id attribute) attribute is the one we're looking for,
375
// return this element; otherwise, traverse it's children.
376
if ( node instanceof Element JavaDoc )
377                 {
378                     if ( collectionMatch( (Element JavaDoc) node, name ) )
379                         return node;
380                     else if ( recurse() )
381                     {
382                         result = namedItem( (Element JavaDoc) node, name );
383                         if ( result != null )
384                             return result;
385                     }
386                 }
387                 node = node.getNextSibling();
388             }
389             return node;
390         }
391     }
392     
393     
394     /**
395      * Returns true if scanning methods should iterate through the collection.
396      * When looking for elements in the document, recursing is needed to traverse
397      * the full document tree. When looking inside a specific element (e.g. for a
398      * cell inside a row), recursing can lead to erroneous results.
399      *
400      * @return True if methods should recurse to traverse entire tree
401      */

402     protected boolean recurse()
403     {
404         return _lookingFor > 0;
405     }
406     
407
408     /**
409      * Determines if current element matches based on what we're looking for.
410      * The element is passed along with an optional identifier name. If the
411      * element is the one we're looking for, return true. If the name is also
412      * specified, the name must match the <code>id</code> attribute
413      * (match <code>name</code> first for anchors).
414      *
415      * @param elem The current element
416      * @param name The identifier name or null
417      * @return The element matches what we're looking for
418      */

419     protected boolean collectionMatch( Element JavaDoc elem, String JavaDoc name )
420     {
421         boolean match;
422         
423         synchronized ( elem )
424         {
425             // Begin with no matching. Depending on what we're looking for,
426
// attempt to match based on the element type. This is the quickest
427
// way to match involving only a cast. Do the expensive string
428
// comparison later on.
429
match = false;
430             switch ( _lookingFor )
431             {
432             case ANCHOR:
433                 // Anchor is an <A> element with a 'name' attribute. Otherwise, it's
434
// just a link.
435
match = ( elem instanceof HTMLAnchorElement ) &&
436                         elem.getAttribute( "name" ).length() > 0;
437                 break;
438             case FORM:
439                 // Any <FORM> element.
440
match = ( elem instanceof HTMLFormElement );
441                 break;
442             case IMAGE:
443                 // Any <IMG> element. <OBJECT> elements with images are not returned.
444
match = ( elem instanceof HTMLImageElement );
445                 break;
446             case APPLET:
447                 // Any <APPLET> element, and any <OBJECT> element which represents an
448
// Applet. This is determined by 'codetype' attribute being
449
// 'application/java' or 'classid' attribute starting with 'java:'.
450
match = ( elem instanceof HTMLAppletElement ) ||
451                         ( elem instanceof HTMLObjectElement &&
452                           ( "application/java".equals( elem.getAttribute( "codetype" ) ) ||
453                             elem.getAttribute( "classid" ).startsWith( "java:" ) ) );
454                 break;
455             case ELEMENT:
456                 // All form elements implement HTMLFormControl for easy identification.
457
match = ( elem instanceof HTMLFormControl );
458                 break;
459             case LINK:
460                 // Any <A> element, and any <AREA> elements with an 'href' attribute.
461
match = ( ( elem instanceof HTMLAnchorElement ||
462                             elem instanceof HTMLAreaElement ) &&
463                           elem.getAttribute( "href" ).length() > 0 );
464                 break;
465             case AREA:
466                 // Any <AREA> element.
467
match = ( elem instanceof HTMLAreaElement );
468                 break;
469             case OPTION:
470                 // Any <OPTION> element.
471
match = ( elem instanceof HTMLOptionElement );
472                 break;
473             case ROW:
474                 // Any <TR> element.
475
match = ( elem instanceof HTMLTableRowElement );
476                 break;
477             case TBODY:
478                 // Any <TBODY> element (one of three table section types).
479
match = ( elem instanceof HTMLTableSectionElement &&
480                           elem.getTagName().equals( "tbody" ) );
481                 break;
482             case CELL:
483                 // Any <TD> element.
484
match = ( elem instanceof HTMLTableCellElement );
485                 break;
486             }
487         
488             // If element type was matched and a name was specified, must also match
489
// the name against either the 'id' or the 'name' attribute. The 'name'
490
// attribute is relevant only for <A> elements for backward compatibility.
491
if ( match && name != null )
492             {
493                 // If an anchor and 'name' attribute matches, return true. Otherwise,
494
// try 'id' attribute.
495
if ( elem instanceof HTMLAnchorElement &&
496                      name.equals( elem.getAttribute( "name" ) ) )
497                     return true;
498                 match = name.equals( elem.getAttribute( "id" ) );
499             }
500         }
501         return match;
502     }
503
504     
505 }
506
507
508 /**
509  * {@link CollectionImpl#item} must traverse down the tree and decrement the
510  * index until it matches an element who's index is zero. Since integers are
511  * passed by value, this class servers to pass the index into each recursion
512  * by reference. It encompasses all the operations that need be performed on
513  * the index, although direct access is possible.
514  *
515  * @see CollectionImpl#item
516  */

517 class CollectionIndex
518 {
519     
520     
521     /**
522      * Returns the current index.
523      *
524      * @return Current index
525      */

526     int getIndex()
527     {
528         return _index;
529     }
530     
531     
532     /**
533      * Decrements the index by one.
534      */

535     void decrement()
536     {
537         -- _index;
538     }
539     
540     
541     /**
542      * Returns true if index is zero (or negative).
543      *
544      * @return True if index is zero
545      */

546     boolean isZero()
547     {
548         return _index <= 0;
549     }
550     
551     
552     /**
553      * Constructs a new index with the specified initial value. The index will
554      * then be decremeneted until it reaches zero.
555      *
556      * @param index The initial value
557      */

558     CollectionIndex( int index )
559     {
560         _index = index;
561     }
562     
563     
564     /**
565      * Holds the actual value that is passed by reference using this class.
566      */

567     private int _index;
568     
569
570 }
571
Popular Tags