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