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: NamespaceStack.java,v 1.6 2001/09/20 10:48:18 jstrachan Exp $
008 */
009
010package org.dom4j.tree;
011
012import java.util.ArrayList;
013import java.util.HashMap;
014import java.util.Map;
015import java.util.List;
016
017import org.dom4j.DocumentFactory;
018import org.dom4j.Namespace;
019import org.dom4j.QName;
020
021/** NamespaceStack implements a stack of namespaces and optionally
022  * maintains a cache of all the fully qualified names (<code>QName</code>)
023  * which are in scope. This is useful when building or navigating a <i>dom4j</i>
024  * document.
025  *
026  * @author <a href="mailto:jstrachan@apache.org">James Strachan</a>
027  * @version $Revision: 1.6 $
028  */
029public class NamespaceStack {
030 
031    /** The factory used to create new <code>Namespace</code> instances */
032    private DocumentFactory documentFactory;
033    
034    /** The Stack of namespaces */
035    private ArrayList namespaceStack = new ArrayList();
036
037    /** The cache of qualifiedNames to QNames per namespace context */
038    private ArrayList namespaceCacheList = new ArrayList();
039
040    /** A cache of current namespace context cache of mapping from qualifiedName to QName */
041    private Map currentNamespaceCache;
042
043    /** A cache of mapping from qualifiedName to QName before any namespaces are declared */
044    private Map rootNamespaceCache = new HashMap();
045
046    
047    /** Caches the default namespace defined via xmlns="" */
048    private Namespace defaultNamespace;
049    
050    
051    public NamespaceStack() {
052        this.documentFactory = DocumentFactory.getInstance();
053    }
054  
055    public NamespaceStack(DocumentFactory documentFactory) {
056        this.documentFactory = documentFactory;
057    }
058  
059    /** Pushes the given namespace onto the stack so that its prefix
060      * becomes available.
061      * 
062      * @param namespace is the <code>Namespace</code> to add to the stack.
063      */
064    public void push(Namespace namespace) {
065        namespaceStack.add( namespace );
066        namespaceCacheList.add( null );
067        currentNamespaceCache = null;
068        String prefix = namespace.getPrefix();
069        if ( prefix == null || prefix.length() == 0 ) {
070            defaultNamespace = namespace;
071        }
072    }      
073    
074    /** Pops the most recently used <code>Namespace</code> from
075      * the stack
076      * 
077      * @return Namespace popped from the stack
078      */
079    public Namespace pop() {
080        return remove( namespaceStack.size() - 1 );
081    }
082    
083    /** @return the number of namespaces on the stackce stack.
084     */
085    public int size() {
086        return namespaceStack.size();     
087    }    
088
089    /** Clears the stack 
090      */
091    public void clear() {
092        namespaceStack.clear();
093        namespaceCacheList.clear();
094        rootNamespaceCache.clear();
095        currentNamespaceCache = null;
096    }
097    
098    /** @return the namespace at the specified index on the stack 
099      */
100    public Namespace getNamespace( int index ) {
101        return (Namespace) namespaceStack.get( index );
102    }
103    
104    /** @return the namespace for the given prefix or null
105      * if it could not be found.
106      */
107    public Namespace getNamespaceForPrefix( String prefix ) {
108        if ( prefix == null ) {
109            prefix = "";
110        }
111        for ( int i = namespaceStack.size() - 1; i >= 0; i-- ) {
112            Namespace namespace = (Namespace) namespaceStack.get(i);
113            if ( prefix.equals( namespace.getPrefix() ) ) {
114                return namespace;
115            }
116        }
117        return null;
118    }
119    
120    /** @return the URI for the given prefix or null if it 
121      * could not be found.
122      */
123    public String getURI( String prefix ) {
124        Namespace namespace = getNamespaceForPrefix( prefix );
125        return ( namespace != null ) ? namespace.getURI() : null;        
126    }
127    
128    /** @return true if the given prefix is in the stack.
129      */
130    public boolean contains( Namespace namespace ) {
131        String prefix = namespace.getPrefix();
132        Namespace current = null;
133        if ( prefix == null || prefix.length() == 0 ) {
134            current = getDefaultNamespace();
135        }
136        else {
137            current = getNamespaceForPrefix( prefix );
138        }
139        if ( current == null ) {
140            return false;
141        }
142        if ( current == namespace ) {
143            return true;
144        }
145        return namespace.getURI().equals( current.getURI() );
146    }
147    
148    public QName getQName( String namespaceURI, String localName, String qualifiedName ) {
149        if ( localName == null ) {
150            localName = qualifiedName;
151        }
152        else if ( qualifiedName == null ) {
153            qualifiedName = localName;
154        }
155        if ( namespaceURI == null ) {
156            namespaceURI = "";
157        }        
158        String prefix = "";
159        int index = qualifiedName.indexOf(":");
160        if (index > 0) {
161            prefix = qualifiedName.substring(0, index);
162        }
163        Namespace namespace = createNamespace( prefix, namespaceURI );
164        return pushQName( localName, qualifiedName, namespace, prefix );
165    }
166
167    public QName getAttributeQName( String namespaceURI, String localName, String qualifiedName ) {
168        if ( qualifiedName == null ) {
169            qualifiedName = localName;
170        }
171        Map map = getNamespaceCache();
172        QName answer = (QName) map.get( qualifiedName );
173        if ( answer != null ) {
174            return answer;
175        }
176        if ( localName == null ) {
177            localName = qualifiedName;
178        }
179        if ( namespaceURI == null ) {
180            namespaceURI = "";
181        }
182        Namespace namespace = null;
183        String prefix = "";
184        int index = qualifiedName.indexOf(":");
185        if (index > 0) {
186            prefix = qualifiedName.substring(0, index);
187            namespace = createNamespace( prefix, namespaceURI );
188        }
189        else {
190            // attributes with no prefix have no namespace
191            namespace = Namespace.NO_NAMESPACE;
192        }
193        answer = pushQName( localName, qualifiedName, namespace, prefix );
194        map.put( qualifiedName, answer );
195        return answer;
196    }
197
198    /** Adds a namepace to the stack with the given prefix and URI */
199    public void push( String prefix, String uri ) {        
200        if ( uri == null ) {
201            uri = "";
202        }
203        Namespace namespace = createNamespace( prefix, uri );
204        push( namespace );
205    }
206    
207    /** Adds a new namespace to the stack */
208    public Namespace addNamespace( String prefix, String uri ) {        
209        Namespace namespace = createNamespace( prefix, uri );
210        push( namespace );
211        return namespace;
212    }
213    
214    /** Pops a namepace from the stack with the given prefix and URI */
215    public Namespace pop( String prefix ) {        
216        if ( prefix == null ) {
217            prefix = "";
218        }
219        Namespace namespace = null;
220        for (int i = namespaceStack.size() - 1; i >= 0; i-- ) {
221            Namespace ns = (Namespace) namespaceStack.get(i);            
222            if ( prefix.equals( ns.getPrefix() ) ) {
223                remove(i);
224                namespace = ns;
225                break;
226            }
227        }
228        if ( namespace == null ) {
229            System.out.println( "Warning: missing namespace prefix ignored: " + prefix );
230        }
231        return namespace;
232    }
233    
234    public String toString() {
235        return super.toString() + " Stack: " + namespaceStack.toString();
236    }
237
238    public DocumentFactory getDocumentFactory() {
239        return documentFactory;
240    }
241    
242    public void setDocumentFactory(DocumentFactory documentFactory) {
243        this.documentFactory = documentFactory;
244    }
245    
246    public Namespace getDefaultNamespace() {
247        if ( defaultNamespace == null ) {
248            defaultNamespace = findDefaultNamespace();
249        }
250        return defaultNamespace;
251    }
252    
253    // Implementation methods
254    //-------------------------------------------------------------------------    
255    
256    /** Adds the QName to the stack of available QNames
257      */
258    protected QName pushQName( String localName, String qualifiedName, Namespace namespace, String prefix ) {
259        if ( prefix == null || prefix.length() == 0 ) {
260            this.defaultNamespace = null;
261        }
262        return createQName( localName, qualifiedName, namespace );
263    }
264
265    /** Factory method to creeate new QName instances. By default this method
266      * interns the QName
267      */
268    protected QName createQName( String localName, String qualifiedName, Namespace namespace ) {
269        return documentFactory.createQName( localName, namespace );
270    }
271    
272    /** Factory method to creeate new Namespace instances. By default this method
273      * interns the Namespace
274      */
275    protected Namespace createNamespace( String prefix, String namespaceURI ) {
276        return documentFactory.createNamespace( prefix, namespaceURI );
277    }
278    
279    /** Attempts to find the current default namespace on the stack right now or returns null if one 
280      * could not be found
281      */
282    protected Namespace findDefaultNamespace() {
283        for ( int i = namespaceStack.size() - 1; i >= 0; i-- ) {
284            Namespace namespace = (Namespace) namespaceStack.get(i);
285            if ( namespace != null ) {
286                String prefix = namespace.getPrefix();
287                if ( prefix == null || namespace.getPrefix().length() == 0 ) {
288                    return namespace;
289                }
290            }
291        }
292        return null;
293    }
294    
295    /** Removes the namespace at the given index of the stack */
296    protected Namespace remove(int index) {
297        Namespace namespace = (Namespace) namespaceStack.remove(index);
298        namespaceCacheList.remove(index);
299        defaultNamespace = null;
300        currentNamespaceCache = null;
301        return namespace;
302    }
303    
304    protected Map getNamespaceCache() {
305        if ( currentNamespaceCache == null ) {
306            int index = namespaceStack.size() - 1;
307            if ( index < 0 ) {
308                currentNamespaceCache = rootNamespaceCache;
309            }
310            else {
311                currentNamespaceCache = (Map) namespaceCacheList.get(index);
312                if ( currentNamespaceCache == null ) {
313                    currentNamespaceCache = new HashMap();
314                    namespaceCacheList.set(index, currentNamespaceCache);
315                }
316            }
317        }
318        return currentNamespaceCache;
319    }
320}
321
322
323
324
325/*
326 * Redistribution and use of this software and associated documentation
327 * ("Software"), with or without modification, are permitted provided
328 * that the following conditions are met:
329 *
330 * 1. Redistributions of source code must retain copyright
331 *    statements and notices.  Redistributions must also contain a
332 *    copy of this document.
333 *
334 * 2. Redistributions in binary form must reproduce the
335 *    above copyright notice, this list of conditions and the
336 *    following disclaimer in the documentation and/or other
337 *    materials provided with the distribution.
338 *
339 * 3. The name "DOM4J" must not be used to endorse or promote
340 *    products derived from this Software without prior written
341 *    permission of MetaStuff, Ltd.  For written permission,
342 *    please contact dom4j-info@metastuff.com.
343 *
344 * 4. Products derived from this Software may not be called "DOM4J"
345 *    nor may "DOM4J" appear in their names without prior written
346 *    permission of MetaStuff, Ltd. DOM4J is a registered
347 *    trademark of MetaStuff, Ltd.
348 *
349 * 5. Due credit should be given to the DOM4J Project
350 *    (http://dom4j.org/).
351 *
352 * THIS SOFTWARE IS PROVIDED BY METASTUFF, LTD. AND CONTRIBUTORS
353 * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
354 * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
355 * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
356 * METASTUFF, LTD. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
357 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
358 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
359 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
360 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
361 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
362 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
363 * OF THE POSSIBILITY OF SUCH DAMAGE.
364 *
365 * Copyright 2001 (C) MetaStuff, Ltd. All Rights Reserved.
366 *
367 * $Id: NamespaceStack.java,v 1.6 2001/09/20 10:48:18 jstrachan Exp $
368 */