View Javadoc
1   /**
2    * Redistribution and use of this software and associated documentation
3    * ("Software"), with or without modification, are permitted provided
4    * that the following conditions are met:
5    *
6    * 1. Redistributions of source code must retain copyright
7    *    statements and notices.  Redistributions must also contain a
8    *    copy of this document.
9    *
10   * 2. Redistributions in binary form must reproduce the
11   *    above copyright notice, this list of conditions and the
12   *    following disclaimer in the documentation and/or other
13   *    materials provided with the distribution.
14   *
15   * 3. The name "Exolab" must not be used to endorse or promote
16   *    products derived from this Software without prior written
17   *    permission of Intalio, Inc.  For written permission,
18   *    please contact info@exolab.org.
19   *
20   * 4. Products derived from this Software may not be called "Exolab"
21   *    nor may "Exolab" appear in their names without prior written
22   *    permission of Intalio, Inc. Exolab is a registered
23   *    trademark of Intalio, Inc.
24   *
25   * 5. Due credit should be given to the Exolab Project
26   *    (http://www.exolab.org/).
27   *
28   * THIS SOFTWARE IS PROVIDED BY INTALIO, INC. AND CONTRIBUTORS
29   * ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
30   * NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
31   * FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
32   * INTALIO, INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
33   * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
34   * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
35   * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
36   * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
37   * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
38   * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
39   * OF THE POSSIBILITY OF SUCH DAMAGE.
40   *
41   * Copyright 2001-2003 (C) Intalio, Inc. All Rights Reserved.
42   *
43   * $Id$
44   */
45  
46  package org.exolab.castor.xml;
47  
48  import java.util.ArrayList;
49  import java.util.Enumeration;
50  import java.util.HashMap;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Map;
54  import org.castor.core.util.Assert;
55  import org.xml.sax.ContentHandler;
56  import org.xml.sax.SAXException;
57  import org.xml.sax.helpers.AttributeListImpl;
58  
59  /**
60   * A class for handling Namespace declaration and scoping
61   * 
62   * @author <a href="mailto:kvisco@intalio.com">Keith Visco</a>
63   * @version $Revision$ $Date: 2004-09-09 23:04:08 -0600 (Thu, 09 Sep
64   *          2004) $
65   **/
66  public final class Namespaces {
67  
68     /**
69      * The reserved XML Namespace Prefix
70      */
71     public static final String XML_NAMESPACE_PREFIX = "xml";
72  
73     /**
74      * The reserved XML 1.0 Namespace URI
75      */
76     public static final String XML_NAMESPACE = "http://www.w3.org/XML/1998/namespace";
77  
78     /**
79      * The CDATA type..uses for SAX attributes
80      */
81     private static final String CDATA = "CDATA";
82  
83     /**
84      * The namespace declaration String
85      **/
86     private static final String XMLNS = "xmlns";
87  
88     /**
89      * Represents a collection of all registered namespaces.
90      */
91     private final List<Namespace> namespaces = new ArrayList<Namespace>();
92  
93     /**
94      * Represents a {@link Map} instance that contains all registered namespaces.
95      */
96     private final Map<String, Namespace> namespaceMap = new HashMap<String, Namespace>();
97  
98     public Namespaces() {
99        super();
100       namespaceMap.put(XML_NAMESPACE_PREFIX, new Namespace(XML_NAMESPACE_PREFIX, XML_NAMESPACE));
101    }
102    /**
103     * Adds the given namespace declaration to this Namespaces instance
104     * 
105     * @param prefix
106     *           the namespace prefix
107     * @param uri
108     *           the namespace URI to be associated with the given prefix
109     * 
110     * @throws IllegalArgumentException
111     *            if uri is null
112     **/
113    public synchronized void addNamespace(String prefix, String uri) {
114 
115       // checks the input parameter
116       Assert.notNull(uri, "Namespace URI must not be null");
117 
118       // -- adjust prefix to prevent null value
119       if (prefix == null)
120          prefix = "";
121 
122       // -- Make sure prefix is not equal to "xml"
123       if (XML_NAMESPACE_PREFIX.equalsIgnoreCase(prefix)) {
124          if (!XML_NAMESPACE.equals(uri)) {
125             String err = "The prefix 'xml' is reserved (XML 1.0 Specification) " + "and cannot be declared.";
126             throw new IllegalArgumentException(err);
127          }
128          // -- if we make it here, just ignore it (it's already supported
129          // internally)
130          return;
131       }
132       // -- make sure URI is not equal to the XML 1.0 namespace
133       else if (XML_NAMESPACE.equals(uri)) {
134          String err = "The namespace '" + XML_NAMESPACE;
135          err += "' is reserved (XML 1.0 Specification) and cannot be declared.";
136          throw new IllegalArgumentException(err);
137       }
138 
139       // adds the namespace
140       Namespace namespace;
141       if (namespaceMap.containsKey(prefix)) {
142          namespaceMap.get(prefix).setUri(uri);
143       } else {
144          namespace = new Namespace(prefix, uri);
145          namespaces.add(namespace);
146          namespaceMap.put(prefix, namespace);
147       }
148    }
149 
150    /**
151     * Returns an Enumeration of local namespace URIs for this Namespaces.
152     * 
153     * @return an Enumeration of local namespace URIs.
154     **/
155    public Enumeration<String> getLocalNamespaces() {
156       return new NamespaceEnumerator(namespaces.iterator());
157    }
158 
159    /**
160     * Returns the Namespace URI associated with the given prefix
161     * 
162     * @param prefix
163     *           the namespace prefix to lookup
164     * @return the namespace URI associated with the given prefix; null if the
165     *         given namespace prefix is not bound.
166     **/
167    public String getNamespaceURI(String prefix) {
168       // -- adjust prefix to prevent null value
169       if (prefix == null)
170          prefix = "";
171 
172 //      // -- handle built-in namespace URIs
173 //      if (XML_NAMESPACE_PREFIX.equals(prefix)) {
174 //         return XML_NAMESPACE;
175 //      }
176 
177       Namespace namespace = namespaceMap.get(prefix);
178 
179       if (namespace != null) {
180          return namespace.getUri();
181       }
182 
183       return null;
184    }
185 
186    /**
187     * Returns the Namespace prefix associated with the given URI. If multiple
188     * namespace prefixes have been declared, then the first one found is
189     * returned. To obtain all prefixes see <code>#getNamespacePrefixes</code>.
190     * 
191     * @param nsURI
192     *           the namespace URI to lookup
193     * @return the namespace prefix associated with the given URI
194     * 
195     * @throws IllegalArgumentException
196     *            if nsURI is null
197     **/
198    public String getNamespacePrefix(String nsURI) {
199 
200       // check the input parameter
201       Assert.notNull(nsURI, "Namespace URI must not be null.");
202 
203       for (Namespace namespace : namespaces) {
204 
205          if (nsURI.equals(namespace.getUri())) {
206             return namespace.getPrefix();
207          }
208       }
209 
210       // -- handle built-in namespace prefixes
211       if (XML_NAMESPACE.equals(nsURI)) {
212          return XML_NAMESPACE_PREFIX;
213       }
214 
215       return null;
216 
217    }
218 
219    /**
220     * Returns all namespace prefixes declared locally
221     * 
222     * @return an Enumeration of locally declared namespace prefixes
223     */
224    public Enumeration<String> getLocalNamespacePrefixes() {
225       return new NamespaceEnumerator(namespaces.iterator(), NamespaceEnumerator.PREFIX);
226    }
227 
228    /**
229     * Returns the Namespace prefixes associated with the given URI.
230     * 
231     * @param nsURI
232     *           the namespace URI to lookup
233     * @param local
234     *           a boolean that when true indicates only the local scope is
235     *           searched.
236     * @return the namespace prefixes associated with the given URI
237     * 
238     * @throws IllegalArgumentException
239     *            if nsURI is null
240     **/
241    public String[] getNamespacePrefixes(String nsURI) {
242 
243       // check the result
244       Assert.notNull(nsURI, "Namespace URI must not be null.");
245 
246       List<String> prefixes = new ArrayList<String>();
247       for (Namespace namespace : namespaces) {
248          if (namespace.getUri().equals(nsURI)) {
249             prefixes.add(namespace.getPrefix());
250          }
251       }
252 
253       return prefixes.toArray(new String[0]);
254    }
255 
256    /**
257     * Returns the Namespace prefix associated with the given URI. Or null if no
258     * prefix has been declared. This method will ignore the default namespace.
259     * This is useful when dealing with attributes that do not use the default
260     * namespace.
261     * 
262     * @param nsURI
263     *           the namespace URI to lookup
264     * @return the namespace prefix associated with the given URI
265     * 
266     * @throws IllegalArgumentException
267     *            if nsURI is null
268     **/
269    public String getNonDefaultNamespacePrefix(String nsURI) {
270       Assert.notNull(nsURI, "Namespace URI must not be null.");
271       for (Namespace namespace : namespaces) {
272          if (nsURI.equals(namespace.getUri()) && namespace.getPrefix().length() > 0) {
273             return namespace.getPrefix();
274          }
275       }
276 
277       // -- handle built-in namespace prefixes
278       if (XML_NAMESPACE.equals(nsURI)) {
279          return XML_NAMESPACE_PREFIX;
280       }
281 
282       return null;
283 
284    }
285 
286    /**
287     * Removes the namespace declaration for the given prefix. This is a local
288     * action only, the namespace declaration will not be removed from any parent
289     * Namespaces object.
290     * 
291     * @param prefix
292     *           the namespace prefix to remove the binding of
293     * @return true if the namespace declaration was removed, otherwise false.
294     */
295    public synchronized boolean removeNamespace(String prefix) {
296       if (prefix == null) {
297          return false;
298       }
299 
300       if (namespaceMap.containsKey(prefix)) {
301          Namespace namespace = namespaceMap.get(prefix);
302          namespaceMap.remove(prefix);
303          namespaces.remove(namespace);
304 
305          return true;
306       }
307 
308       return false;
309    }
310 
311    /**
312     * Calls the given ContentHandler's endPrefixMapping method for each locally
313     * declared namespace
314     * 
315     * @param handler
316     *           the ContentHandler
317     */
318    public void sendEndEvents(ContentHandler handler) throws SAXException {
319       for (Namespace namespace : namespaces) {
320          handler.endPrefixMapping(namespace.getPrefix());
321       }
322    }
323 
324    /**
325     * Calls the given ContentHandler's startPrefixMapping method for each
326     * locally declared namespace
327     * 
328     * @param handler
329     *           the ContentHandler
330     */
331    public void sendStartEvents(ContentHandler handler) throws SAXException {
332       for (Namespace namespace : namespaces) {
333          handler.startPrefixMapping(namespace.getPrefix(), namespace.getUri());
334       }
335    }
336 
337    /**
338     * Declare the namespaces of this stack in as attributes.
339     * 
340     * @param atts
341     *           the Attribute List to fill in.
342     */
343    @SuppressWarnings("deprecation")
344    public void declareAsAttributes(AttributeListImpl atts) {
345 
346       String attName = null;
347       for (Namespace ns : namespaces) {
348          if (ns.prefix != null) {
349             int len = ns.prefix.length();
350             if (len > 0) {
351                StringBuffer buf = new StringBuffer(6 + len);
352                buf.append(XMLNS);
353                buf.append(':');
354                buf.append(ns.prefix);
355                attName = buf.toString();
356                atts.addAttribute(attName, CDATA, ns.uri);
357             }
358             // case with no prefix but a nsURI
359             else {
360                atts.addAttribute(XMLNS, CDATA, ns.uri);
361             }
362          } // ns.prefix!=null
363          else {
364             atts.addAttribute(XMLNS, CDATA, ns.uri);
365          }
366       }
367    }
368 
369    /**
370     * An internal class used to represent an XML namespace.
371     **/
372    class Namespace {
373 
374       /**
375        * The namespace uri.
376        */
377       private String uri;
378       
379       /**
380        * The namespace prefix bound to the uri.
381        */
382       private String prefix;
383 
384 
385       /**
386        * Creates new {@link Namespace} instance, the namespace prefix and uri
387        * remains uninitialized.
388        */
389       Namespace() {
390          super();
391       }
392 
393       Namespace(String prefix, String uri) {
394          this.prefix = prefix;
395          this.uri = uri;
396       }
397 
398       /**
399        * Retrieves the namespace prefix.
400        * 
401        * @return the namespace prefix
402        */
403       public String getPrefix() {
404          return prefix;
405       }
406 
407       /**
408        * Sets the namespace prefix
409        * 
410        * @param prefix
411        *           the namespace prefix
412        */
413       public void setPrefix(String prefix) {
414          this.prefix = prefix;
415       }
416 
417       /**
418        * Retrieves the namespace uri.
419        * 
420        * @return the namespace uri
421        */
422       public String getUri() {
423          return uri;
424       }
425 
426       /**
427        * Sets the namespace uri
428        * 
429        * @param uri
430        *           the namespace uri
431        */
432       public void setUri(String uri) {
433          this.uri = uri;
434       }
435 
436       /**
437        * {@inheritDoc}
438        */
439       @Override
440       public boolean equals(Object object) {
441          if (this == object) {
442             return true;
443          }
444          if (object == null || getClass() != object.getClass()) {
445             return false;
446          }
447 
448          Namespace namespace = (Namespace) object;
449 
450          if (prefix != null ? !prefix.equals(namespace.prefix) : namespace.prefix != null)
451             return false;
452          if (uri != null ? !uri.equals(namespace.uri) : namespace.uri != null)
453             return false;
454 
455          return true;
456       }
457 
458       /**
459        * {@inheritDoc}
460        */
461       @Override
462       public int hashCode() {
463          int result = prefix != null ? prefix.hashCode() : 0;
464          result = 31 * result + (uri != null ? uri.hashCode() : 0);
465          return result;
466       }
467    }
468 
469    /**
470     * A simple Enumeration for Namespace objects
471     */
472    static class NamespaceEnumerator implements java.util.Enumeration<String> {
473       public static final int URI = 0;
474       public static final int PREFIX = 1;
475 
476       private int _returnType = URI;
477 
478       private Iterator<Namespace> namespaceIterator;
479 
480       NamespaceEnumerator(Iterator<Namespace> namespaceIterator) {
481          this.namespaceIterator = namespaceIterator;
482       }
483 
484       NamespaceEnumerator(Iterator<Namespace> namespaceIterator, int returnType) {
485          this.namespaceIterator = namespaceIterator;
486          _returnType = returnType;
487       }
488 
489       public boolean hasMoreElements() {
490          return namespaceIterator.hasNext();
491       }
492 
493       public String nextElement() {
494 
495          String result;
496          Namespace ns = namespaceIterator.next();
497 
498          if (_returnType == URI) {
499             result = ns.getUri();
500          } else {
501             result = ns.getPrefix();
502          }
503 
504          return result;
505       }
506 
507    }
508 
509 }