001: /*
002: *
003: *
004: * Copyright 1990-2007 Sun Microsystems, Inc. All Rights Reserved.
005: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER
006: *
007: * This program is free software; you can redistribute it and/or
008: * modify it under the terms of the GNU General Public License version
009: * 2 only, as published by the Free Software Foundation.
010: *
011: * This program is distributed in the hope that it will be useful, but
012: * WITHOUT ANY WARRANTY; without even the implied warranty of
013: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
014: * General Public License version 2 for more details (a copy is
015: * included at /legal/license.txt).
016: *
017: * You should have received a copy of the GNU General Public License
018: * version 2 along with this work; if not, write to the Free Software
019: * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
020: * 02110-1301 USA
021: *
022: * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa
023: * Clara, CA 95054 or visit www.sun.com if you need additional
024: * information or have any questions.
025: */
026: package com.sun.perseus.model;
027:
028: import org.w3c.dom.Node;
029: import org.w3c.dom.DOMException;
030:
031: /**
032: * <code>CompositeNode</code> models <code>ModelNodes</code which
033: * can have children {@link ElementNode ElementNodes}.
034: *
035: * <p>A <code>CompositeNode</code> can have either <code>ModelNode</code>
036: * children and text content children (see the
037: * {@link #appendTextChild appendTextChild} method).
038: *
039: * @version $Id: CompositeNode.java,v 1.9 2006/06/29 10:47:30 ln156897 Exp $
040: */
041: public abstract class CompositeNode extends ModelNode {
042: /**
043: * The first child
044: */
045: protected ElementNode firstChild;
046:
047: /**
048: * The last child
049: */
050: protected ElementNode lastChild;
051:
052: /**
053: * Clears the text layouts, if any exist. This is typically
054: * called when the font selection has changed and nodes such
055: * as <code>Text</code> should recompute their layouts.
056: * This should recursively call clearLayouts on children
057: * node or expanded content, if any.
058: */
059: protected void clearLayouts() {
060: clearLayouts(firstChild);
061: }
062:
063: /**
064: * @return this node's first child.
065: */
066: public ModelNode getFirstChildNode() {
067: return firstChild;
068: }
069:
070: /**
071: * @return this node's last child.
072: */
073: public ModelNode getLastChildNode() {
074: return lastChild;
075: }
076:
077: /**
078: * Utility method. Unhooks the children.
079: */
080: protected void unhookChildrenQuiet() {
081: unhookQuiet(firstChild);
082: firstChild = null;
083: lastChild = null;
084: }
085:
086: /**
087: * Appends an element at the end of the list
088: *
089: * @param element the node to add to this <tt>CompositeNode</tt>
090: * @throws NullPointerException if the input argument is null.
091: */
092: public void add(final ElementNode element) {
093: if (element == null) {
094: throw new NullPointerException();
095: }
096:
097: element.preValidate();
098:
099: if (firstChild == null) {
100: firstChild = element;
101: lastChild = element;
102: element.nextSibling = null;
103: element.prevSibling = null;
104: } else {
105: lastChild.nextSibling = element;
106: element.nextSibling = null;
107: element.prevSibling = lastChild;
108: lastChild = element;
109:
110: }
111:
112: // An insertion event is triggered from the setParent call.
113: element.setParent(this );
114: }
115:
116: // =========================================================================
117: // Node interface implementation.
118: // =========================================================================
119:
120: /**
121: * @return the namespace URI of the Node.
122: *
123: * @throws SecurityException if the application does not have the necessary
124: * privilege rights to access this (SVG) content.
125: */
126: public abstract String getNamespaceURI();
127:
128: /**
129: * @return unprefixed node name. For an SVGElement, this returns the tag
130: * name without a prefix. In case of the Document node, string
131: * <code>#document</code> is returned.
132: * @throws SecurityException if the application does not have the necessary
133: * privilege rights to access this (SVG) content.
134: */
135: public abstract String getLocalName();
136:
137: /**
138: * Returns the parent <code>Node</code> of this <code>Node</code>.
139: *
140: * @return the parent node or null if there is no parent (i.e. if a node has
141: * just been created and not yet added to the tree, or if it has been
142: * removed from the tree, this is null).
143: * @throws SecurityException if the application does not have the necessary
144: * privilege rights to access this (SVG) content.
145: */
146: public abstract Node getParentNode();
147:
148: // JAVADOC COMMENT ELIDED
149: public Node appendChild(final Node newChild) throws DOMException {
150: return insertBefore(newChild, null);
151: }
152:
153: // JAVADOC COMMENT ELIDED
154: public Node removeChild(final Node oldChild) throws DOMException {
155: // First, check if this is indeed a child of this node.
156: if (!isChild(oldChild)) {
157: if (oldChild == null) {
158: throw new NullPointerException();
159: }
160:
161: throw new DOMException(DOMException.NOT_FOUND_ERR, Messages
162: .formatMessage(Messages.ERROR_NOT_A_CHILD, null));
163: }
164:
165: // Now, check if this is a supported operation
166: if (!isRemoveChildSupported()) {
167: throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
168: Messages.formatMessage(
169: Messages.ERROR_REMOVE_CHILD_NOT_SUPPORTED,
170: new String[] { getLocalName(),
171: getNamespaceURI() }));
172: }
173:
174: // Check if the removed child, or one of its descendants, has
175: // an identifier.
176: ElementNode oldNode = (ElementNode) oldChild;
177: if (isIdBranch(oldNode)) {
178: throw new DOMException(DOMException.INVALID_ACCESS_ERR,
179: Messages.formatMessage(
180: Messages.ERROR_CANNOT_REMOVE_NODE_WITH_ID,
181: null));
182: }
183:
184: if (oldNode.nextSibling != null) {
185: oldNode.nextSibling.prevSibling = oldNode.prevSibling;
186: } else {
187: lastChild = (ElementNode) oldNode.prevSibling;
188: }
189:
190: if (oldNode.prevSibling != null) {
191: oldNode.prevSibling.nextSibling = oldNode.nextSibling;
192: } else {
193: firstChild = (ElementNode) oldNode.nextSibling;
194: }
195:
196: oldNode.nextSibling = null;
197: oldNode.prevSibling = null;
198: oldNode.setParent(null);
199:
200: return oldChild;
201: }
202:
203: // JAVADOC COMMENT ELIDED
204: public Node insertBefore(final Node newChild, final Node refChild)
205: throws DOMException {
206: if (newChild == null) {
207: throw new NullPointerException();
208: }
209:
210: if (newChild == ownerDocument) {
211: throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
212: Messages.formatMessage(
213: Messages.ERROR_CANNOT_INSERT_DOCUMENT_NODE,
214: null));
215: }
216:
217: // The DocumentNode class can only create ElementNode instances. If we
218: // are dealing with an object which is not an ElementNode instance, we
219: // know we are dealing with something in the wrong document.
220: if (!(newChild instanceof ElementNode)) {
221: throw new DOMException(
222: DOMException.WRONG_DOCUMENT_ERR,
223: Messages
224: .formatMessage(
225: Messages.ERROR_CANNOT_INSERT_FROM_OTHER_DOCUMENT,
226: null));
227: }
228:
229: ElementNode newNode = (ElementNode) newChild;
230:
231: // Check if newNode belongs to the same document.
232: if (newNode.ownerDocument != ownerDocument) {
233: throw new DOMException(
234: DOMException.WRONG_DOCUMENT_ERR,
235: Messages
236: .formatMessage(
237: Messages.ERROR_CANNOT_INSERT_FROM_OTHER_DOCUMENT,
238: null));
239: }
240:
241: // Check if the newNode is of a type allowed by this Node
242: // implementation.
243: if (!isAllowedChild(newNode)) {
244: throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
245: Messages
246: .formatMessage(
247: Messages.ERROR_CHILD_NOT_ALLOWED,
248: new String[] {
249: newNode.getLocalName(),
250: newNode.getNamespaceURI(),
251: getLocalName(),
252: getNamespaceURI() }));
253: }
254:
255: // Check if the newNode is one of this node's ancestor, or the
256: // node itself.
257: if (isAncestor(newNode)) {
258: throw new DOMException(DOMException.HIERARCHY_REQUEST_ERR,
259: Messages.formatMessage(
260: Messages.ERROR_INSERTING_ANCESTOR, null));
261: }
262:
263: // Check if this node is of type document and already has a child.
264: if (this == ownerDocument) {
265: if (firstChild != null) {
266: throw new DOMException(
267: DOMException.HIERARCHY_REQUEST_ERR,
268: Messages
269: .formatMessage(
270: Messages.ERROR_INSERTING_UNDER_DOCUMENT,
271: null));
272: }
273: }
274:
275: // Check refChild if refChild is not null
276: if (refChild != null) {
277: if (!isChild(refChild)) {
278: throw new DOMException(DOMException.NOT_FOUND_ERR,
279: Messages.formatMessage(
280: Messages.ERROR_REF_NODE_NOT_A_CHILD,
281: null));
282: }
283: }
284:
285: // Check that newChild is _not_ a child of the DocumentNode
286: if (newNode.parent == ownerDocument) {
287: throw new DOMException(DOMException.NOT_SUPPORTED_ERR,
288: Messages.formatMessage(
289: Messages.ERROR_INSERTING_DOCUMENT_ELEMENT,
290: null));
291: }
292:
293: // If the inserted node already has a parent, remove the node
294: // from its current parent.
295: if (newNode.parent != null) {
296: ((CompositeNode) newNode.parent).removeChild(newNode);
297: }
298:
299: if (refChild == null) {
300: // Performs the Use xlink:href attribute validation
301: add(newNode);
302: } else {
303: // Because refChild's parent is this node, we know it has to
304: // be an ElementNode. So the following cast is safe. See
305: // isChild call above.
306: ElementNode refNode = (ElementNode) refChild;
307: newNode.prevSibling = refNode.prevSibling;
308: newNode.nextSibling = refNode;
309: if (refNode.prevSibling != null) {
310: // refNode is _not_ the first node
311: refNode.prevSibling.nextSibling = newNode;
312: } else {
313: // refNode _is_ the first node
314: firstChild = newNode;
315: }
316: refNode.prevSibling = newNode;
317: newNode.nextSibling = refNode;
318:
319: // An insertion event is triggered from the setParent call
320: newNode.setParent(this );
321: }
322:
323: return newChild;
324: }
325:
326: /**
327: * Implementation helper. By default, we only disallow SVG nodes under
328: * all nodes except DocumentNode.
329: *
330: * @param node the candidate child node.
331: * @return true if the input node can be inserted under this CompositeNode
332: */
333: protected boolean isAllowedChild(final ElementNode node) {
334: if (node instanceof SVG) {
335: return false;
336: }
337: return true;
338: }
339:
340: /**
341: * Implementation helper.
342: *
343: * @param node the node which may be an ancestor of this CompositeNode.
344: * @return true if the input node is this node or if it is one of its
345: * ancestors.
346: */
347: final boolean isAncestor(final ElementNode node) {
348: if (node == this ) {
349: return true;
350: } else if (parent != null) {
351: return ((CompositeNode) parent).isAncestor(node);
352: } else {
353: return false;
354: }
355: }
356:
357: /**
358: * Implementation helper.
359: *
360: * @param node the node which may be a child of this composite node.
361: * @return true if the input node is one of this composite's children.
362: */
363: final boolean isChild(final Node node) {
364: ElementNode c = firstChild;
365: while (c != null) {
366: if (c == node) {
367: return true;
368: }
369: c = (ElementNode) c.nextSibling;
370: }
371: return false;
372: }
373:
374: /**
375: * @return true if this node supports removing children. By default, this
376: * returns true.
377: */
378: protected boolean isRemoveChildSupported() {
379: return true;
380: }
381:
382: /**
383: * @param node the root of the branch to check for ids. Should not be null.
384: * @return true if the input node, or any of its descendant, has an id.
385: */
386: protected static boolean isIdBranch(final ElementNode node) {
387: if (node.id != null) {
388: return true;
389: }
390:
391: ElementNode c = node.firstChild;
392: while (c != null) {
393: if (isIdBranch(c)) {
394: return true;
395: } else {
396: c = (ElementNode) c.nextSibling;
397: }
398: }
399:
400: return false;
401: }
402:
403: /**
404: * When a CompositeNode is hooked into the document tree, by default,
405: * it notifies its children and calls its own nodeHookedInDocumentTree
406: * method.
407: */
408: final void onHookedInDocumentTree() {
409: super .onHookedInDocumentTree();
410:
411: nodeHookedInDocumentTree();
412:
413: ModelNode c = getFirstExpandedChild();
414: while (c != null) {
415: c.onHookedInDocumentTree();
416: c = c.nextSibling;
417: }
418:
419: c = getFirstChildNode();
420: while (c != null) {
421: c.onHookedInDocumentTree();
422: c = c.nextSibling;
423: }
424: }
425:
426: /**
427: * When a CompositeNode is hooked into the document tree, by default,
428: * it notifies its children and calls its own nodeUnhookedFromDocumentTree
429: * method.
430: */
431: final void onUnhookedFromDocumentTree() {
432: super .onUnhookedFromDocumentTree();
433:
434: nodeUnhookedFromDocumentTree();
435:
436: ModelNode c = getFirstExpandedChild();
437: while (c != null) {
438: c.onUnhookedFromDocumentTree();
439: c = c.nextSibling;
440: }
441:
442: c = getFirstChildNode();
443: while (c != null) {
444: c.onUnhookedFromDocumentTree();
445: c = c.nextSibling;
446: }
447: }
448:
449: /**
450: * To be overriddent by derived classes, such as TimedElementNode,
451: * if they need to do special operations when hooked into the
452: * document tree.
453: */
454: void nodeHookedInDocumentTree() {
455: }
456:
457: /**
458: * To be overriddent by derived classes, such as TimedElementNode,
459: * if they need to do special operations when unhooked from the
460: * document tree.
461: */
462: void nodeUnhookedFromDocumentTree() {
463: }
464:
465: }
|