1 /* Copyright 2002-2005 Elliotte Rusty Harold
2 
3    This library is free software; you can redistribute it and/or modify
4    it under the terms of version 2.1 of the GNU Lesser General Public
5    License as published by the Free Software Foundation.
6 
7    This library is distributed in the hope that it will be useful,
8    but WITHOUT ANY WARRANTY; without even the implied warranty of
9    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10    GNU Lesser General Public License for more details.
11 
12    You should have received a copy of the GNU Lesser General Public
13    License along with this library; if not, write to the
14    Free Software Foundation, Inc., 59 Temple Place, Suite 330,
15    Boston, MA 02111-1307  USA
16 
17    You can contact Elliotte Rusty Harold by sending e-mail to
18    elharo@ibiblio.org. Please include the word "XOM" in the
19    subject line. The XOM home page is located at http://www.xom.nu/
20 */
21 
22 package nu.xom;
23 
24 
25 /**
26  *
27  * <p>
28  * The generic superclass for nodes that have children.
29  * Not counting subclasses, there are exactly two such classes in XOM:
30  * </p>
31  *
32  * <ul>
33  *   <li><code>Document</code></li>
34  *   <li><code>Element</code></li>
35  * </ul>
36  *
37  * <p>
38  * This class provides methods to add and remove child nodes.
39  * </p>
40  *
41  *
42  * @author Elliotte Rusty Harold
43  * @version 1.2.3
44  *
45  */
46 public abstract class ParentNode extends Node {
47 
48     Node[] children;
49     int    childCount = 0;
50     String actualBaseURI;
51 
52     /**
53      * <p>
54      * Creates a new <code>ParentNode</code> object.
55      * Can only be invoked by other members of
56      * the <code>nu.xom</code> package.
57      * </p>
58      *
59      */
ParentNode()60     ParentNode() {}
61 
62 
63     /**
64      * <p>
65      * Returns the number of child nodes this node contains.
66      * This is always greater than or equal to 0.
67      * </p>
68      *
69      * @return the number of children of this node
70      */
getChildCount()71     public int getChildCount() {
72         return childCount;
73     }
74 
75 
76     /**
77      * <p>
78      * Inserts a child node at the specified position.
79      * The child node previously at that position (if any)
80      * and all subsequent child nodes are moved up by one.
81      * That is, when inserting a node at 2, the old node at 2
82      * is moved to 3, the old child at 3 is moved to 4, and so
83      * forth. Inserting at position 0 makes the child the first
84      * child of this node. Inserting at the position
85      * <code>getChildCount()</code> makes the child the
86      * last child of the node.
87      * </p>
88      *
89      * <p>
90      * All the other methods that add a node to the tree ultimately
91      * invoke this method.
92      * </p>
93      *
94      * @param position where to insert the child
95      * @param child the node to insert
96      *
97      * @throws IllegalAddException if this node cannot have a child of
98      *     the argument's type
99      * @throws MultipleParentException if <code>child</code> already
100      *     has a parent
101      * @throws NullPointerException if <code>child</code> is null
102      * @throws IndexOutOfBoundsException if the position is negative or
103      *     greater than the number of children of this node
104      */
insertChild(Node child, int position)105     public void insertChild(Node child, int position) {
106         _insertChild(child, position);
107     }
108 
109 
110     // because this method is called from Document constructor and
111     // constructors should not call overridable methods
_insertChild(Node child, int position)112     final void _insertChild(Node child, int position) {
113         insertionAllowed(child, position);
114         fastInsertChild(child, position);
115     }
116 
117 
fastInsertChild(Node child, int position)118     void fastInsertChild(Node child, int position) {
119         if (position > childCount) {
120             throw new IndexOutOfBoundsException("Inserted node at position " + position + " after children");
121         }
122         checkCapacity(childCount+1);
123         if (position < childCount) {
124             // make space in the middle for the new child; otherwise just add it at the end
125             System.arraycopy(children, position, children, position+1, childCount-position);
126         }
127         children[position] = child;
128         childCount++;
129         child.setParent(this);
130     }
131 
132 
checkCapacity(int position)133     private void checkCapacity(int position) {
134 
135         if (children == null) {
136             children = new Node[1];
137         }
138         else if (position >= children.length) {
139             Node[] data = new Node[children.length * 2];
140             System.arraycopy(children, 0, data, 0, children.length);
141             this.children = data;
142         }
143 
144     }
145 
146 
insertionAllowed(Node child, int position)147     abstract void insertionAllowed(Node child, int position);
148 
149 
150     /**
151      * <p>
152      * Appends a node to the children of this node.
153      * </p>
154      *
155      * @param child node to append to this node
156      *
157      * @throws IllegalAddException if this node cannot have children
158      *     of this type
159      * @throws MultipleParentException if child already has a parent
160      * @throws NullPointerException if <code>child</code> is null
161      *
162      */
appendChild(Node child)163     public void appendChild(Node child) {
164         insertChild(child, childCount);
165     }
166 
167 
168     /**
169      *<p>
170      * Returns the child of this node at the specified position.
171      * Indexes begin at 0 and count up to one less than the number
172      * of children in this node.
173      * </p>
174      *
175      * @param position index of the node to return
176      *
177      * @return the node at the requested position
178      *
179      * @throws IndexOutOfBoundsException if the index is negative or
180      *     greater than or equal to the number of children of this node
181      */
getChild(int position)182     public Node getChild(int position) {
183 
184         if (children == null) {
185             throw new IndexOutOfBoundsException(
186               "This node has no children"
187             );
188         }
189         return children[position];
190 
191     }
192 
193 
194     // private int lastPosition = -1;
195 
196     /**
197      *<p>
198      * Returns the position of a node within the children of this
199      * node. This is a number between 0 and one less than the number of
200      * children of this node. It returns -1 if <code>child</code>
201      * does not have this node as a parent.
202      * </p>
203      *
204      * <p>
205      * This method does a linear search through the node's children.
206      * On average, it executes in O(N) where N is the number of
207      * children of the node.
208      * </p>
209      *
210      * @param child the node whose position is desired
211      *
212      * @return the position of the argument node among
213      *     the children of this node
214      */
indexOf(Node child)215     public int indexOf(Node child) {
216 
217         if (children == null) return -1;
218 
219         // Programs tend to iterate through in order so we store the
220         // last index returned and check the one immediately after it
221         //  first; before searching the list from the beginning.
222         /* lastPosition++;
223         if (lastPosition != children.size()) {
224             if (child == children.get(lastPosition)) {
225               return lastPosition;
226             }
227             else lastPosition = -1;
228         }
229         lastPosition = children.indexOf(child);
230         return lastPosition; */
231         for (int i = 0; i < childCount; i++) {
232             if (child == children[i]) return i;
233         }
234         return -1;
235 
236     }
237 
238 
239     /**
240      * <p>
241      * Removes the child of this node at the specified position.
242      * Indexes begin at 0 and count up to one less than the number
243      * of children in this node.
244      * </p>
245      *
246      * @param position index of the node to remove
247      *
248      * @return the node which was removed
249      *
250      * @throws IndexOutOfBoundsException if the index is negative or
251      *     greater than or equal to the number of children of this node
252      */
removeChild(int position)253     public Node removeChild(int position) {
254 
255         if (children == null) {
256             throw new IndexOutOfBoundsException(
257               "This node has no children"
258             );
259         }
260         Node removed = children[position];
261         // fill in actual base URI
262         // This way does add base URIs to elements created in memory
263         // XXX but this is a HotSpot when building; we need a fastRemoveChild
264         if (removed.isElement()) fillInBaseURI((Element) removed);
265 
266         int toCopy = childCount-position-1;
267         if (toCopy > 0) {
268             System.arraycopy(children, position+1, children, position, toCopy);
269         }
270         childCount--;
271         children[childCount] = null;
272         removed.setParent(null);
273 
274         return removed;
275 
276     }
277 
278 
fillInBaseURI(Element removed)279     void fillInBaseURI(Element removed) {
280 
281         ParentNode parent = removed;
282         String actualBaseURI = "";
283         while (parent != null && actualBaseURI.equals("")) {
284             actualBaseURI = parent.getActualBaseURI();
285             parent = parent.getParent();
286         }
287         removed.setActualBaseURI(actualBaseURI);
288 
289     }
290 
291 
292     /**
293      * <p>
294      * Removes the specified child of this node.
295      * </p>
296      *
297      * @param child child node to remove
298      *
299      * @return the node which was removed
300      *
301      * @throws NoSuchChildException if <code>child</code> is
302      *     not in fact a child of this node
303      */
removeChild(Node child)304     public Node removeChild(Node child) {
305 
306         if (children == null) {
307             throw new NoSuchChildException(
308               "Child does not belong to this node"
309             );
310         }
311         // This next line is a hot spot
312         int position = indexOf(child);
313         if (position == -1) {
314             throw new NoSuchChildException(
315               "Child does not belong to this node"
316             );
317         }
318         if (child.isElement()) fillInBaseURI((Element) child);
319         removeChild(position);
320 
321         child.setParent(null);
322 
323         return child;
324 
325     }
326 
327 
328     /**
329      * <p>
330      * Replaces an existing child with a new child node.
331      * If <code>oldChild</code> is not a child of this node,
332      * then a <code>NoSuchChildException</code> is thrown.
333      * </p>
334      *
335      * @param oldChild the node removed from the tree
336      * @param newChild the node inserted into the tree
337      *
338      * @throws MultipleParentException if <code>newChild</code> already
339      *     has a parent
340      * @throws NoSuchChildException if <code>oldChild</code>
341      *     is not a child of this node
342      * @throws NullPointerException if either argument is null
343      * @throws IllegalAddException if this node cannot have children
344      *     of the type of <code>newChild</code>
345      */
replaceChild(Node oldChild, Node newChild)346     public void replaceChild(Node oldChild, Node newChild) {
347 
348         if (oldChild == null) {
349             throw new NullPointerException(
350               "Tried to replace null child"
351             );
352         }
353         if (newChild == null) {
354             throw new NullPointerException(
355               "Tried to replace child with null"
356             );
357         }
358         if (children == null) {
359             throw new NoSuchChildException(
360               "Reference node is not a child of this node."
361             );
362         }
363         int position = indexOf(oldChild);
364         if (position == -1)  {
365             throw new NoSuchChildException(
366               "Reference node is not a child of this node."
367             );
368         }
369 
370         if (oldChild == newChild) return;
371 
372         insertionAllowed(newChild, position);
373         removeChild(position);
374         insertChild(newChild, position);
375 
376     }
377 
378 
379     /**
380      *
381      * <p>
382      * Sets the URI against which relative URIs in this node will be
383      * resolved. Generally, it's only necessary to set this property if
384      * it's different from a node's parent's base URI, as it may
385      * be in a document assembled from multiple entities
386      * or by XInclude.
387      * </p>
388      *
389      * <p>
390      * Relative URIs are not allowed here. Base URIs must be absolute.
391      * However, the base URI may be set to null or the empty string
392      * to indicate that the node has no explicit base URI. In this
393      * case, it inherits the base URI of its parent node, if any.
394      * </p>
395      *
396      * <p>
397      * URIs with fragment identifiers are also not allowed. The value
398      * passed to this method must be a pure URI, not a URI reference.
399      * </p>
400      *
401      * <p>
402      * You can also add an <code>xml:base</code> attribute to
403      * an element in the same way you'd add any other namespaced
404      * attribute to an element. If an element's base URI
405      * conflicts with its <code>xml:base</code> attribute,
406      * then the value found in the <code>xml:base</code> attribute
407      * is used.
408      * </p>
409      *
410      * <p>
411      * If the base URI is null or the empty string and there is
412      * no <code>xml:base</code> attribute, then the base URI is
413      * determined by the nearest ancestor node which does have a
414      * base URI. Moving such a node from one location to another
415      * can change its base URI.
416      * </p>
417      *
418      * @param URI the new base URI for this node
419      *
420      * @throws MalformedURIException if <code>URI</code> is
421      *     not a legal RFC 3986 absolute URI
422      */
setBaseURI(String URI)423     public abstract void setBaseURI(String URI);
424 
425 
getActualBaseURI()426     String getActualBaseURI() {
427         if (actualBaseURI == null) return "";
428         return actualBaseURI;
429     }
430 
431 
setActualBaseURI(String uri)432     void setActualBaseURI(String uri) {
433         if (uri == null) uri = "";
434         if (!"".equals(uri)) Verifier.checkAbsoluteURI(uri);
435         actualBaseURI = uri;
436     }
437 
438 
findActualBaseURI()439     final String findActualBaseURI() {
440 
441         ParentNode current = this;
442         while (true) {
443             String actualBase = current.getActualBaseURI();
444             ParentNode parent = current.getParent();
445 
446             if (parent == null) return actualBase;
447 
448             if ("".equals(actualBase)) {
449                 current = parent;
450                 continue;
451             }
452 
453             // The parent is loaded from a different entity.
454             // Therefore just return the actual base.
455             return actualBase;
456         }
457 
458     }
459 
460 
461 }
462