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 }