001    /*
002     * Copyright (c) 2007 Henri Sivonen
003     * Copyright (c) 2008 Mozilla Foundation
004     *
005     * Permission is hereby granted, free of charge, to any person obtaining a 
006     * copy of this software and associated documentation files (the "Software"), 
007     * to deal in the Software without restriction, including without limitation 
008     * the rights to use, copy, modify, merge, publish, distribute, sublicense, 
009     * and/or sell copies of the Software, and to permit persons to whom the 
010     * Software is furnished to do so, subject to the following conditions:
011     *
012     * The above copyright notice and this permission notice shall be included in 
013     * all copies or substantial portions of the Software.
014     *
015     * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
016     * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
017     * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL 
018     * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
019     * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 
020     * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
021     * DEALINGS IN THE SOFTWARE.
022     */
023    
024    package nu.validator.saxtree;
025    
026    import org.xml.sax.Locator;
027    
028    /**
029     * Common superclass for parent nodes.
030     * @version $Id$
031     * @author hsivonen
032     */
033    public abstract class ParentNode extends Node {
034    
035        /**
036         * The end locator.
037         */
038        protected Locator endLocator;
039        
040        /**
041         * The first child.
042         */
043        private Node firstChild = null;
044        
045        /**
046         * The last child (for efficiency).
047         */
048        private Node lastChild = null;
049        
050        /**
051         * The constuctor.
052         * @param locator the locator
053         */
054        ParentNode(Locator locator) {
055            super(locator);
056        }
057    
058        /**
059         * Sets the endLocator.
060         * 
061         * @param endLocator the endLocator to set
062         */
063        public void setEndLocator(Locator endLocator) {
064            this.endLocator = new LocatorImpl(endLocator);
065        }
066    
067        /**
068         * Copies the endLocator from another node.
069         * 
070         * @param another the another node
071         */
072        public void copyEndLocator(ParentNode another) {
073            this.endLocator = another.endLocator;
074        }
075        
076        /**
077         * Returns the firstChild.
078         * 
079         * @return the firstChild
080         */
081        public final Node getFirstChild() {
082            return firstChild;
083        }
084    
085        /**
086         * Returns the lastChild.
087         * 
088         * @return the lastChild
089         */
090        public final Node getLastChild() {
091            return lastChild;
092        }
093    
094        /**
095         * Insert a new child before a pre-existing child and return the newly inserted child.
096         * @param child the new child
097         * @param sibling the existing child before which to insert (must be a child of this node) or <code>null</code> to append
098         * @return <code>child</code>
099         */
100        public Node insertBefore(Node child, Node sibling) {
101            assert sibling == null || this == sibling.getParentNode();
102            if (sibling == null) {
103                return appendChild(child);
104            }
105            child.detach();
106            child.setParentNode(this);
107            if (firstChild == sibling) {
108                child.setNextSibling(sibling);
109                firstChild = child;
110            } else {
111                Node prev = firstChild;
112                Node next = firstChild.getNextSibling();
113                while (next != sibling) {
114                    prev = next;
115                    next = next.getNextSibling();
116                }
117                prev.setNextSibling(child);
118                child.setNextSibling(next);
119            }
120            return child;
121        }
122        
123        public Node insertBetween(Node child, Node prev, Node next) {
124            assert prev == null || this == prev.getParentNode();
125            assert next == null || this == next.getParentNode();
126            assert prev != null || next == firstChild;
127            assert next != null || prev == lastChild;
128            assert prev == null || next == null || prev.getNextSibling() == next;
129            if (next == null) {
130                return appendChild(child);            
131            }
132            child.detach();
133            child.setParentNode(this);
134            child.setNextSibling(next);
135            if (prev == null) {
136                firstChild = child;
137            } else {
138                prev.setNextSibling(child);
139            }        
140            return child;
141        }
142        
143        /**
144         * Append a child to this node and return the child.
145         * 
146         * @param child the child to append.
147         * @return <code>child</code>
148         */
149        public Node appendChild(Node child) {
150            child.detach();
151            child.setParentNode(this);
152            if (firstChild == null) {
153                firstChild = child;
154            } else {
155                lastChild.setNextSibling(child);
156            }
157            lastChild = child;
158            return child;
159        }
160        
161        /**
162         * Append the children of another node to this node removing them from the other node .
163         * @param parent the other node whose children to append to this one
164         */
165        public void appendChildren(Node parent) {
166            Node child = parent.getFirstChild();
167            if (child == null) {
168                return;
169            }
170            ParentNode another = (ParentNode) parent;
171            if (firstChild == null) {
172                firstChild = child;
173            } else {
174                lastChild.setNextSibling(child);
175            }
176            lastChild = another.lastChild;
177            do {
178                child.setParentNode(this);
179            } while ((child = child.getNextSibling()) != null);
180            another.firstChild = null;
181            another.lastChild = null;
182        }
183    
184        /**
185         * Remove a child from this node.
186         * @param node the child to remove
187         */
188        void removeChild(Node node) {
189            assert this == node.getParentNode();
190            if (firstChild == node) {
191                firstChild = node.getNextSibling();
192                if (lastChild == node) {
193                    lastChild = null;
194                }
195            } else {
196                Node prev = firstChild;
197                Node next = firstChild.getNextSibling();
198                while (next != node) {
199                    prev = next;
200                    next = next.getNextSibling();
201                }
202                prev.setNextSibling(node.getNextSibling());
203                if (lastChild == node) {
204                    lastChild = prev;
205                }            
206            }
207        }
208    }