001: /**
002: * org/ozone-db/xml/dom/CollectionImpl.java
003: *
004: * The contents of this file are subject to the OpenXML Public
005: * License Version 1.0; you may not use this file except in compliance
006: * with the License. You may obtain a copy of the License at
007: * http://www.openxml.org/license.html
008: *
009: * THIS SOFTWARE IS DISTRIBUTED ON AN "AS IS" BASIS WITHOUT WARRANTY
010: * OF ANY KIND, EITHER EXPRESSED OR IMPLIED. THE INITIAL DEVELOPER
011: * AND ALL CONTRIBUTORS SHALL NOT BE LIABLE FOR ANY DAMAGES AS A
012: * RESULT OF USING, MODIFYING OR DISTRIBUTING THIS SOFTWARE OR ITS
013: * DERIVATIVES. SEE THE LICENSE FOR THE SPECIFIC LANGUAGE GOVERNING
014: * RIGHTS AND LIMITATIONS UNDER THE LICENSE.
015: *
016: * The Initial Developer of this code under the License is Assaf Arkin.
017: * Portions created by Assaf Arkin are Copyright (C) 1998, 1999.
018: * All Rights Reserved.
019: */
020:
021: /**
022: * Changes for Persistent DOM running with ozone are
023: * Copyright 1999 by SMB GmbH. All rights reserved.
024: */package org.ozoneDB.xml.dom;
025:
026: import org.w3c.dom.*;
027: import org.w3c.dom.html.*;
028:
029: //import org.openxml.XMLCollection;
030:
031: /**
032: * Implements a live collection of elements. This collection is based on the
033: * {@link org.w3c.dom.html.HTMLCollection} defined for HTML documents but works
034: * with XML documents.
035: * <P>
036: * The collection is defined in terms of a root element and the elements to look
037: * for under this root. Only elements of the specified type are contained in the
038: * collection. Elements are returned by index or by identifier (the <TT>id</TT>
039: * attribute). The collection is live, meaning that changes to the document tree
040: * are immediately reflected in the collection. The collection is not optimized for
041: * traversing large document trees.
042: * <P>
043: * The collection has to meet two requirements: it has to be live, and it has
044: * to traverse depth first and always return results in that order. As such,
045: * using an object container (such as {@link java.util.Vector}) is expensive on
046: * insert/remove operations. Instead, the collection has been implemented using
047: * three traversing functions. As a result, operations on large documents will
048: * result in traversal of the entire document tree and consume a considerable
049: * amount of time. * <P> * Note that synchronization on the traversed document cannot be achieved.
050: * The document itself cannot be locked, and locking each traversed node is
051: * likely to lead to a dead lock condition. Therefore, there is a chance of the
052: * document being changed as results are fetched; in all likelihood, the results
053: * might be out dated, but not erroneous.
054: * <P>
055: * Used to implement both {@link org.openxml.XMLCollection} and {@link
056: * org.openxml.dom.html.HTMLAnchorElementImpl}. *
057: *
058: * @version $Revision: 1.1 $ $Date: 2001/12/18 11:03:24 $
059: * @author <a href="mailto:arkin@trendline.co.il">Assaf Arkin</a>
060: * @see org.w3c.dom.html.HTMLCollection
061: */
062: public class CollectionImpl implements HTMLCollection {
063:
064: /**
065: * Returns the length of the collection. This method might traverse the entire
066: * document tree.
067: *
068: * @return Length of the collection
069: */
070: public final int getLength() {
071: // Call recursive function on top-level element.
072: return getLength(getTopLevel());
073: }
074:
075: /**
076: * Retrieves the indexed node from the collection. Nodes are numbered in tree
077: * order - depth-first traversal order. This method might traverse the entire
078: * document tree.
079: *
080: * @param index The index of the node to return
081: * @return The specified node or null if no such node found
082: */
083: public final Node item(int index) {
084: if (index < 0) {
085: throw new IllegalArgumentException(
086: "Argument 'index' is negative.");
087: }
088: // Call recursive function on top-level element.
089: return item(getTopLevel(), new CollectionIndex(index));
090: }
091:
092: /**
093: * Retrieves the named node from the collection. The name is matched case
094: * sensitive against the <TT>id</TT> attribute of each element in the
095: * collection, returning the first match. The tree is traversed in depth-first
096: * order. This method might traverse the entire document tree.
097: *
098: * @param name The name of the node to return
099: * @return The specified node or null if no such node found
100: */
101: public final Node namedItem(String name) {
102: if (name == null) {
103: throw new NullPointerException("Argument 'name' is null.");
104: }
105: // Call recursive function on top-level element.
106: return namedItem(getTopLevel(), name);
107: }
108:
109: /**
110: * Returns the top level element underneath which the collection exists.
111: *
112: * @return Top level element from which to scan
113: */
114: private Element getTopLevel() {
115: return _topLevel;
116: }
117:
118: /**
119: * Recursive function returns the number of elements of a particular type that
120: * exist under the top level element. This is a recursive function and the
121: * top level element is passed along.
122: *
123: * @param topLevel Top level element from which to scan
124: * @return Number of elements
125: */
126: private int getLength(Element topLevel) {
127: int length;
128: Node node;
129:
130: synchronized (topLevel) {
131: // Always count from zero and traverse all the childs of the current
132: // element in the order they appear.
133: length = 0;
134: node = topLevel.getFirstChild();
135: while (node != null) {
136: // If a particular node is an element (could be HTML or XML), do two
137: // things: if it's the one we're looking for, count another matched // element; at any rate, traverse it's children as well.
138: if (node instanceof Element) {
139: if (collectionMatch((Element) node, null)) {
140: ++length;
141: }
142: if (recurse()) {
143: length += getLength((Element) node);
144: }
145: }
146: node = node.getNextSibling();
147: }
148: }
149: return length;
150: }
151:
152: /**
153: * Recursive function returns the numbered element of a particular type that
154: * exist under the top level element. This is a recursive function and the
155: * top level element is passed along.
156: * <p>
157: * Note that this function must call itself with an index and get back both
158: * the element (if one was found) and the new index which is decremeneted
159: * for any like element found. Since integers are only passed by value, this
160: * function makes use of a separate class ({@link CollectionIndex}) to
161: * hold that index.
162: *
163: * @param topLevel Top level element from which to scan
164: * @param index The index of the item to retreive
165: * @return Number of elements
166: * @see CollectionIndex
167: */
168: private Node item(Element topLevel, CollectionIndex index) {
169: Node node;
170: Node result;
171:
172: synchronized (topLevel) {
173: // Traverse all the childs of the current element in the order they appear.
174: // Count from the index backwards until you reach matching element with an
175: // index of zero. Return that element.
176: node = topLevel.getFirstChild();
177: while (node != null) {
178: // If a particular node is an element (could be HTML or XML), do two
179: // things: if it's the one we're looking for, decrease the index and
180: // if zero, return this node; at any rate, traverse it's children
181: // as well.
182: if (node instanceof Element) {
183: if (collectionMatch((Element) node, null)) {
184: if (index.isZero()) {
185: return node;
186: }
187: index.decrement();
188: }
189: if (recurse()) {
190: result = item((Element) node, index);
191: if (result != null) {
192: return result;
193: }
194: }
195: }
196: node = node.getNextSibling();
197: }
198: }
199: return null;
200: }
201:
202: /**
203: * Recursive function returns an element of a particular type with the
204: * specified name (<TT>ID</TT> attribute).
205: *
206: * @param topLevel Top level element from which to scan
207: * @param name The named element to look for
208: * @return The first named element found
209: */
210: private Node namedItem(Element topLevel, String name) {
211: Node node;
212: Node result;
213: synchronized (topLevel) {
214: // Traverse all the childs of the current element in the order they appear.
215: node = topLevel.getFirstChild();
216: while (node != null) {
217: // If a particular node is an element (could be HTML or XML),
218: // do two things: if it's the one we're looking for, and the name
219: // (ID attribute) attribute is the one we're looking for, return
220: // this element; otherwise, traverse it's children.
221: if (node instanceof Element) {
222: if (collectionMatch((Element) node, name)) {
223: return node;
224: }
225: if (recurse()) {
226: result = namedItem((Element) node, name);
227: if (result != null) {
228: return result;
229: }
230: }
231: }
232:
233: node = node.getNextSibling();
234: }
235: return node;
236: }
237: }
238:
239: /**
240: * Returns true if scanning methods should iterate through the collection.
241: * When looking for elements in the document, recursing is needed to traverse
242: * the full document tree. When looking inside a specific element (e.g. for a
243: * cell inside a row), recursing can lead to erroneous results.
244: *
245: * @return True if methods should recurse to traverse entire tree
246: */
247: protected boolean recurse() {
248: return true;
249: }
250:
251: /**
252: * Determines if current element matches based on what we're looking for.
253: * The element is passed along with an optional identifier name. If the
254: * element is the one we're looking for, return true. If the name is also
255: * specified, the name must match the <TT>id</TT> attribute.
256: *
257: * @param elem The current element
258: * @param name The identifier name or null
259: * @return The element matches what we're looking for
260: */
261: protected boolean collectionMatch(Element elem, String name) {
262: boolean match;
263: synchronized (elem) {
264: match = elem.getTagName().equals(_lookForTag);
265: if (match && name != null) {
266: match = name.equals(elem.getAttribute("id"));
267: }
268: }
269: return match;
270: }
271:
272: /**
273: * Construct a new collection that retrieves element of the specific type
274: * (<TT>lookFor</TT>) from the specific document portion (<TT>topLevel</TT>).
275: *
276: * @param topLevel The element underneath which the collection exists
277: * @param lookFor Tag of element to look for
278: */
279: public CollectionImpl(Element topLevel, String lookFor) {
280: if (topLevel == null) {
281: throw new NullPointerException(
282: "Argument 'topLevel' is null.");
283: }
284: if (lookFor == null || lookFor.length() == 0) {
285: throw new NullPointerException(
286: "Argument 'lookFor' is null or an empty string.");
287: }
288: _topLevel = topLevel;
289: _lookForTag = lookFor;
290: }
291:
292: /**
293: * Hidden constructor used by derived classes that might have different
294: * _lookfor properties.
295: *
296: * @param topLevel The element underneath which the collection exists
297: */
298: public CollectionImpl(Element topLevel) {
299: if (topLevel == null) {
300: throw new NullPointerException(
301: "Argument 'topLevel' is null.");
302: }
303: _topLevel = topLevel;
304: }
305:
306: /**
307: * This is the top level element underneath which the collection exists.
308: */
309: private Element _topLevel;
310: /**
311: * This is the element type that this collection deals with. It identifies
312: * the element's tag name, and only elements matching these tag names are
313: * counted or returned by this collection.
314: */
315: private String _lookForTag;
316:
317: }
318:
319: /**
320: * {@link CollectionImpl#item} must traverse down the tree and decrement the
321: * index until it matches an element who's index is zero. Since integers are
322: * passed by value, this class servers to pass the index into each recursion
323: * by reference. It encompasses all the operations that need be performed on
324: * the index, although direct access is possible.
325: *
326: * @see CollectionImpl#item
327: */
328: class CollectionIndex {
329:
330: /**
331: * Returns the current index.
332: *
333: * @return Current index
334: */
335: int getIndex() {
336: return _index;
337: }
338:
339: /**
340: * Decrements the index by one.
341: */
342: void decrement() {
343: --_index;
344: }
345:
346: /**
347: * Returns true if index is zero (or negative).
348: *
349: * @return True if index is zero
350: */
351: boolean isZero() {
352: return _index <= 0;
353: }
354:
355: /**
356: * Constructs a new index with the specified initial value. The index will
357: * then be decremeneted until it reaches zero.
358: *
359: * @param index The initial value
360: */
361: CollectionIndex(int index) {
362: _index = index;
363: }
364:
365: /**
366: * Holds the actual value that is passed by reference using this class.
367: */
368: private int _index;
369:
370: }
|