001/*
002 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
003 * 
004 * This software is open source. 
005 * See the bottom of this file for the licence.
006 * 
007 * $Id: AbstractBranch.java,v 1.36 2001/07/25 10:51:11 jstrachan Exp $
008 */
009
010package org.dom4j.tree;
011
012import java.io.IOException;
013import java.io.StringWriter;
014import java.io.PrintWriter;
015import java.util.ArrayList;
016import java.util.HashMap;
017import java.util.Iterator;
018import java.util.LinkedList;
019import java.util.List;
020import java.util.Map;
021import java.util.StringTokenizer;
022
023import org.dom4j.Branch;
024import org.dom4j.CDATA;
025import org.dom4j.CharacterData;
026import org.dom4j.Comment;
027import org.dom4j.Element;
028import org.dom4j.Entity;
029import org.dom4j.IllegalAddException;
030import org.dom4j.Node;
031import org.dom4j.Namespace;
032import org.dom4j.QName;
033import org.dom4j.ProcessingInstruction;
034import org.dom4j.Text;
035import org.dom4j.io.OutputFormat;
036
037import org.xml.sax.Attributes;
038
039/** <p><code>AbstractBranch</code> is an abstract base class for 
040  * tree implementors to use for implementation inheritence.</p>
041  *
042  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
043  * @version $Revision: 1.36 $
044  */
045public abstract class AbstractBranch extends AbstractNode implements Branch {
046
047    /** The output format used by default */
048    protected static final OutputFormat outputFormat = new OutputFormat();
049
050    protected static final int DEFAULT_CONTENT_LIST_SIZE = 5;
051    
052    
053    public AbstractBranch() { 
054    }
055
056    
057    public boolean isReadOnly() {
058        return false;
059    }    
060    
061    public boolean hasContent() {
062        return nodeCount() > 0;
063    }
064
065    public List content() {
066        List backingList = contentList();
067        return new ContentListFacade(this, backingList);
068    }
069    
070    public String getText() {
071        List content = contentList();
072        if (content != null) {
073            int size = content.size();
074            if (size >= 1) {
075                Object first = content.get(0);
076                String firstText = getContentAsText( first );
077                if (size == 1) {
078                    // optimised to avoid StringBuffer creation
079                    return firstText;
080                }
081                else {
082                    StringBuffer buffer = new StringBuffer( firstText );
083                    for ( int i = 1; i < size; i++ ) {
084                        Object node = content.get(i);
085                        buffer.append( getContentAsText( node ) );
086                    }
087                    return buffer.toString();
088                }
089            }
090        }
091        return "";
092    }
093
094    /** @return the text value of the given content object
095     * as text which returns the text value of CDATA, Entity or Text nodes
096     */
097    protected String getContentAsText(Object content) {
098        if ( content instanceof Node) {
099            Node node = (Node) content;
100            switch ( node.getNodeType() ) {
101                case CDATA_SECTION_NODE:
102                //case ENTITY_NODE:
103                case ENTITY_REFERENCE_NODE:
104                case TEXT_NODE:
105                    return node.getText();
106            }
107        }
108        else if ( content instanceof String) {
109            return (String) content;
110        }
111        return "";
112    }
113
114    /** @return the XPath defined string-value of the given content object
115     */
116    protected String getContentAsStringValue(Object content) {
117        if ( content instanceof Node) {
118            Node node = (Node) content;
119            switch ( node.getNodeType() ) {
120                case CDATA_SECTION_NODE:
121                //case ENTITY_NODE:
122                case ENTITY_REFERENCE_NODE:
123                case TEXT_NODE:
124                case ELEMENT_NODE:
125                    return node.getStringValue();
126            }
127        }
128        else if ( content instanceof String) {
129            return (String) content;
130        }
131        return "";
132    }
133
134    
135    public String getTextTrim() {
136        String text = getText();
137
138        StringBuffer textContent = new StringBuffer();
139        StringTokenizer tokenizer = new StringTokenizer(text);
140        while (tokenizer.hasMoreTokens()) {
141            String str = tokenizer.nextToken();
142            textContent.append(str);
143            if (tokenizer.hasMoreTokens()) {
144                textContent.append(" ");  // separator
145            }
146        }
147
148        return textContent.toString();
149    }
150
151    public void setProcessingInstructions(List listOfPIs) {
152        for ( Iterator iter = listOfPIs.iterator(); iter.hasNext(); ) {
153            ProcessingInstruction pi = (ProcessingInstruction) iter.next();
154            addNode(pi);
155        }
156    }
157    
158    public Element addElement(String name) {
159        Element node = getDocumentFactory().createElement( name );
160        add( node );
161        return node;
162    }
163    
164    public Element addElement(String qualifiedName, String namespaceURI) {
165        Element node = getDocumentFactory().createElement( qualifiedName, namespaceURI );
166        add( node );
167        return node;
168    }
169    
170    public Element addElement(QName qname) {
171        Element node = getDocumentFactory().createElement( qname );
172        add( node );
173        return node;
174    }
175    
176    public Element addElement(String name, String prefix, String uri) {
177        Namespace namespace = Namespace.get( prefix, uri );
178        QName qName = getDocumentFactory().createQName( name, namespace );
179        return addElement( qName );
180    }
181    
182    // polymorphic node methods    
183
184    public void add(Node node) {
185        switch ( node.getNodeType() ) {
186            case ELEMENT_NODE:
187                add((Element) node);
188                break;
189            case COMMENT_NODE:
190                add((Comment) node);
191                break;
192            case PROCESSING_INSTRUCTION_NODE:
193                add((ProcessingInstruction) node);
194                break;
195            default:
196                invalidNodeTypeAddException(node);
197        }
198    }
199    
200    public boolean remove(Node node) {
201        switch ( node.getNodeType() ) {
202            case ELEMENT_NODE:
203                return remove((Element) node);
204            case COMMENT_NODE:
205                return remove((Comment) node);
206            case PROCESSING_INSTRUCTION_NODE:
207                return remove((ProcessingInstruction) node);
208            default:
209                invalidNodeTypeAddException(node);
210                return false;
211        }
212    }
213    
214    // typesafe versions using node classes
215    
216    public void add(Comment comment) {
217        addNode(comment);
218    }
219    
220    public void add(Element element) {
221        addNode(element);
222    }
223    
224    public void add(ProcessingInstruction pi) {
225        addNode(pi);
226    }
227    
228    public boolean remove(Comment comment) {
229        return removeNode(comment);
230    }
231    
232    public boolean remove(Element element) {
233        return removeNode(element);
234    }
235    
236    public boolean remove(ProcessingInstruction pi) {
237        return removeNode(pi);
238    }
239    
240    
241    public Element elementByID(String elementID) {
242        for ( int i = 0, size = nodeCount(); i < size; i++ ) {
243            Node node = node(i);
244            if ( node instanceof Element ) {
245                Element element = (Element) node;
246                String id = elementID(element);
247                if ( id != null && id.equals( elementID ) ) {
248                    return element;
249                }
250                else {
251                    element = element.elementByID( elementID );
252                    if ( element != null ) {
253                        return element;
254                    }
255                }
256            }
257        }
258        return null;
259    }
260    
261    public void appendContent(Branch branch) {
262        for ( int i = 0, size = branch.nodeCount(); i < size; i++ ) {
263            Node node = branch.node(i);
264            add( (Node) node.clone() );
265        }
266    }
267        
268    
269    public Node node(int index) {
270        Object object = contentList().get(index);
271        if (object instanceof Node) {
272            return (Node) object;
273        }
274        if (object instanceof String) {
275            return getDocumentFactory().createText(object.toString());
276        }
277        return null;
278    }
279    
280    public int nodeCount() {
281        return contentList().size();
282    }
283    
284    public int indexOf(Node node) {
285        return contentList().indexOf( node );
286    }
287    
288    public Iterator nodeIterator() {
289        return contentList().iterator();
290    }
291
292    
293    // Implementation methods
294    
295    /** @return the ID of the given <code>Element</code>
296      */
297    protected String elementID(Element element) {
298        // XXX: there will be other ways of finding the ID
299        // XXX: should probably have an IDResolver or something
300        return element.attributeValue( "ID" );
301    }
302    
303    /** @return the internal List used to manage the content */
304    protected abstract List contentList();
305
306    /** A Factory Method pattern which creates 
307      * a List implementation used to store content
308      */
309    protected List createContentList() {
310        return new ArrayList( DEFAULT_CONTENT_LIST_SIZE );
311    }
312    
313    /** A Factory Method pattern which creates 
314      * a List implementation used to store content
315      */
316    protected List createContentList(int size) {
317        return new ArrayList( size );
318    }
319    
320    
321    /** A Factory Method pattern which creates 
322      * a BackedList implementation used to store results of 
323      * a filtered content query.     */
324    protected BackedList createResultList() {
325        return new BackedList( this, contentList() );
326    }
327    
328    /** A Factory Method pattern which creates 
329      * a BackedList implementation which contains a single result
330      */
331    protected List createSingleResultList( Object result ) {
332        BackedList list = new BackedList( this, contentList(), 1 );
333        list.addLocal( result );
334        return list;
335    }
336    
337    /** A Factory Method pattern which creates an empty
338      * a BackedList implementation
339      */
340    protected List createEmptyList() {
341        return new BackedList( this, contentList(), 0 );
342    }
343    
344    
345    protected abstract void addNode(Node node);
346    
347    protected abstract boolean removeNode(Node node);
348    
349    
350    /** Called when a new child node has been added to me
351      * to allow any parent relationships to be created or
352      * events to be fired.
353      */
354    protected abstract void childAdded(Node node);
355    
356    /** Called when a child node has been removed 
357      * to allow any parent relationships to be deleted or
358      * events to be fired.
359      */
360    protected abstract void childRemoved(Node node);
361
362    /** Called when the given List content has been removed so
363      * each node should have its parent and document relationships
364      * cleared
365      */
366    protected void contentRemoved() {
367        List content = contentList();
368        for ( int i = 0, size = content.size(); i < size; i++ ) {
369            Object object = content.get(i);
370            if ( object instanceof Node ) {
371                 childRemoved( (Node) object );
372            }            
373        }
374    }
375
376    /** Called when an invalid node has been added. 
377      * Throws an {@link IllegalAddException}.
378      */
379    protected void invalidNodeTypeAddException(Node node) {
380        throw new IllegalAddException( "Invalid node type. Cannot add node: " + node + " to this branch: " + this );
381    }
382    
383
384}
385
386
387
388
389/*
390 * Redistribution and use of this software and associated documentation
391 * ("Software"), with or without modification, are permitted provided
392 * that the following conditions are met:
393 *
394 * 1. Redistributions of source code must retain copyright
395 *    statements and notices.  Redistributions must also contain a
396 *    copy of this document.
397 *
398 * 2. Redistributions in binary form must reproduce the
399 *    above copyright notice, this list of conditions and the
400 *    following disclaimer in the documentation and/or other
401 *    materials provided with the distribution.
402 *
403 * 3. The name "DOM4J" must not be used to endorse or promote
404 *    products derived from this Software without prior written
405 *    permission of MetaStuff, Ltd.  For written permission,
406 *    please contact dom4j-info@metastuff.com.
407 *
408 * 4. Products derived from this Software may not be called "DOM4J"
409 *    nor may "DOM4J" appear in their names without prior written
410 *    permission of MetaStuff, Ltd. DOM4J is a registered
411 *    trademark of MetaStuff, Ltd.
412 *
413 * 5. Due credit should be given to the DOM4J Project
414 *    (http://dom4j.org/).
415 *
416 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
417 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
418 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
419 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
420 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
421 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
422 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
423 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
424 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
425 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
426 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
427 * OF THE POSSIBILITY OF SUCH DAMAGE.
428 *
429 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
430 *
431 * $Id: AbstractBranch.java,v 1.36 2001/07/25 10:51:11 jstrachan Exp $
432 */