001: /*
002: * $Id: StartElementEvent.java,v 1.5 2004/07/15 02:11:01 cniles Exp $
003: *
004: * Copyright (c) 2004, Christian Niles, unit12.net
005: * All rights reserved.
006: *
007: * Redistribution and use in source and binary forms, with or without
008: * modification, are permitted provided that the following conditions are met:
009: *
010: * * Redistributions of source code must retain the above copyright
011: * notice, this list of conditions and the following disclaimer.
012: *
013: * * Redistributions in binary form must reproduce the above copyright
014: * notice, this list of conditions and the following disclaimer in the
015: * documentation and/or other materials provided with the distribution.
016: *
017: * * Neither the name of Christian Niles, Unit12, nor the names of its
018: * contributors may be used to endorse or promote products derived from
019: * this software without specific prior written permission.
020: *
021: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
022: * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
023: * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
024: * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
025: * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
026: * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
027: * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
028: * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
029: * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
030: * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
031: * POSSIBILITY OF SUCH DAMAGE.
032: *
033: */
034: package javanet.staxutils.events;
035:
036: import java.util.ArrayList;
037: import java.util.Collections;
038: import java.util.HashMap;
039: import java.util.Iterator;
040: import java.util.List;
041: import java.util.Map;
042:
043: import javax.xml.namespace.NamespaceContext;
044: import javax.xml.namespace.QName;
045: import javax.xml.stream.Location;
046: import javax.xml.stream.events.Attribute;
047: import javax.xml.stream.events.Namespace;
048: import javax.xml.stream.events.StartElement;
049:
050: import javanet.staxutils.NamespaceContextAdapter;
051: import javanet.staxutils.StaticNamespaceContext;
052:
053: /**
054: * {@link StartElement} event implementation. This event will coalesce its namespaces
055: * into an internal {@link NamespaceContext}, available via
056: * {@link #getNamespaceContext()}. It will also create any implicit namespaces
057: * necessary to satisfy the element's name and those of its attributes.
058: *
059: * @author Christian Niles
060: * @version $Revision: 1.5 $
061: */
062: public class StartElementEvent extends AbstractXMLEvent implements
063: StartElement {
064:
065: /** The qualified element name. */
066: protected QName name;
067:
068: /** The element attributes. */
069: protected Map attributes;
070:
071: /** The element namespaces. */
072: protected Map namespaces;
073:
074: /** The namespace context. */
075: protected NamespaceContext namespaceCtx;
076:
077: public StartElementEvent(QName name, NamespaceContext namespaceCtx,
078: Location location) {
079:
080: super (location);
081: this .name = name;
082: this .namespaceCtx = new StartElementContext(namespaceCtx);
083:
084: }
085:
086: public StartElementEvent(QName name, Iterator attributes,
087: Iterator namespaces, NamespaceContext namespaceCtx,
088: Location location, QName schemaType) {
089:
090: super (location, schemaType);
091: this .namespaceCtx = new StartElementContext(namespaceCtx);
092:
093: mergeNamespaces(namespaces);
094: mergeAttributes(attributes);
095:
096: QName newName = processQName(name);
097: this .name = (newName == null ? name : newName);
098:
099: }
100:
101: public StartElementEvent(StartElement that) {
102:
103: this (that.getName(), that.getAttributes(),
104: that.getNamespaces(), that.getNamespaceContext(), that
105: .getLocation(), that.getSchemaType());
106:
107: }
108:
109: /** Returns {@link #START_ELEMENT}. */
110: public int getEventType() {
111:
112: return START_ELEMENT;
113:
114: }
115:
116: public QName getName() {
117:
118: return name;
119:
120: }
121:
122: public Attribute getAttributeByName(QName name) {
123:
124: if (attributes != null) {
125:
126: return (Attribute) attributes.get(name);
127:
128: } else {
129:
130: return null;
131:
132: }
133:
134: }
135:
136: public Iterator getAttributes() {
137:
138: if (attributes != null) {
139:
140: return attributes.values().iterator();
141:
142: } else {
143:
144: return Collections.EMPTY_LIST.iterator();
145:
146: }
147:
148: }
149:
150: public NamespaceContext getNamespaceContext() {
151:
152: return namespaceCtx;
153:
154: }
155:
156: public Iterator getNamespaces() {
157:
158: if (namespaces != null) {
159:
160: return namespaces.values().iterator();
161:
162: } else {
163:
164: return Collections.EMPTY_LIST.iterator();
165:
166: }
167:
168: }
169:
170: public String getNamespaceURI(String prefix) {
171:
172: return getNamespaceContext().getNamespaceURI(prefix);
173:
174: }
175:
176: /**
177: * Performs the task of adding {@link Attribute}s into the internal
178: * {@link #attributes} map. Along the way, it will also create the necessary
179: * {@link Namespace} events to satisfy attribute namespaces.
180: *
181: * @param iter An iterator over a set of {@link Attributes}.
182: */
183: private void mergeAttributes(Iterator iter) {
184:
185: if (iter == null) {
186:
187: return;
188:
189: }
190:
191: while (iter.hasNext()) {
192:
193: Attribute attr = (Attribute) iter.next();
194:
195: if (attributes == null) {
196:
197: attributes = new HashMap();
198:
199: }
200:
201: // check if the attribute QName has the proper mapping
202: QName attrName = attr.getName();
203: QName newName = processQName(attrName);
204: if (newName != null) {
205:
206: // need to generate a new attribute with the new qualified name
207: Attribute newAttr = new AttributeEvent(newName, null,
208: attr);
209: attributes.put(newName, newAttr);
210:
211: } else {
212:
213: // the attribute is fine
214: attributes.put(attrName, attr);
215:
216: }
217:
218: }
219:
220: }
221:
222: /**
223: * Performs the task of adding {@link Namespace}s into the internal
224: * {@link #namespaces} map.
225: *
226: * @param iter An iterator over a set of {@link Namespaces}.
227: */
228: private void mergeNamespaces(Iterator iter) {
229:
230: if (iter == null) {
231:
232: return;
233:
234: }
235:
236: // for each namespace, add it to the context, and place it in the list
237: while (iter.hasNext()) {
238:
239: Namespace ns = (Namespace) iter.next();
240: String prefix = ns.getPrefix();
241:
242: if (namespaces == null) {
243:
244: namespaces = new HashMap();
245:
246: }
247:
248: if (!namespaces.containsKey(prefix)) {
249:
250: namespaces.put(prefix, ns);
251:
252: }
253:
254: }
255:
256: }
257:
258: /**
259: * Processes a {@link QName}, possibly rewriting it to match the current
260: * namespace context. If necessary, a new {@link Namespace} will be added to
261: * support the name's prefix.
262: *
263: * @param name The {@link QName} to process.
264: * @return The new name, or <code>null</code> if no changes were necessary.
265: */
266: private QName processQName(QName name) {
267:
268: String nsURI = name.getNamespaceURI();
269: String prefix = name.getPrefix();
270:
271: if (nsURI == null || nsURI.length() == 0) {
272:
273: // name belongs to no namespace. This can only be okay if the name is
274: // an attribute name, or the default namespace hasn't been overridden.
275: // either way, no prefix should be allowed on the name, so the best we
276: // can do is rewrite the name to make sure no prefix is present
277:
278: // clear any prefix from the name
279: if (prefix != null && prefix.length() > 0) {
280:
281: return new QName(name.getLocalPart());
282:
283: } else {
284:
285: return name;
286:
287: }
288:
289: }
290:
291: // namespace uri is non-null after this point
292:
293: String resolvedNS = namespaceCtx.getNamespaceURI(prefix);
294: if (resolvedNS == null) {
295:
296: // if the prefix is not empty, then we should default the prefix
297: if (prefix != null && prefix.length() > 0) {
298:
299: if (namespaces == null) {
300:
301: namespaces = new HashMap();
302:
303: }
304: namespaces.put(prefix,
305: new NamespaceEvent(prefix, nsURI));
306:
307: }
308:
309: return null;
310:
311: } else if (!resolvedNS.equals(nsURI)) {
312:
313: // The prefix is bound to a different namespace, so we'll have to
314: // search for existing prefixes bound to the namespace uri, or
315: // generate a new namespace binding.
316: String newPrefix = namespaceCtx.getPrefix(nsURI);
317: if (newPrefix == null) {
318:
319: // no existing prefix; need to generate a new prefix
320: newPrefix = generatePrefix(nsURI);
321:
322: }
323:
324: // return the newly prefixed name
325: return new QName(nsURI, name.getLocalPart(), newPrefix);
326:
327: } else {
328:
329: // prefix has already been bound to the namespace; nothing to do
330: return null;
331:
332: }
333:
334: }
335:
336: /**
337: * Generates a new namespace prefix for the specified namespace URI that
338: * doesn't collide with any existing prefix.
339: *
340: * @param nsURI The URI for which to generate a prefix.
341: * @return The new prefix.
342: */
343: private String generatePrefix(String nsURI) {
344:
345: String newPrefix;
346: int nsCount = 0;
347: do {
348:
349: newPrefix = "ns" + nsCount;
350: nsCount++;
351:
352: } while (namespaceCtx.getNamespaceURI(newPrefix) != null);
353:
354: if (namespaces == null) {
355:
356: namespaces = new HashMap();
357:
358: }
359: namespaces.put(newPrefix, new NamespaceEvent(newPrefix, nsURI));
360:
361: return newPrefix;
362:
363: }
364:
365: /**
366: * Adapts another {@link NamespaceContext} to expose this tag's declared
367: * namespaces.
368: *
369: * @author Christian Niles
370: * @version $Revision: 1.5 $
371: */
372: private final class StartElementContext extends
373: NamespaceContextAdapter implements StaticNamespaceContext {
374:
375: public StartElementContext(NamespaceContext namespaceCtx) {
376:
377: super (namespaceCtx);
378:
379: }
380:
381: public String getNamespaceURI(String prefix) {
382:
383: if (namespaces != null && namespaces.containsKey(prefix)) {
384:
385: Namespace namespace = (Namespace) namespaces
386: .get(prefix);
387: return namespace.getNamespaceURI();
388:
389: } else {
390:
391: return super .getNamespaceURI(prefix);
392:
393: }
394:
395: }
396:
397: public String getPrefix(String nsURI) {
398:
399: for (Iterator i = getNamespaces(); i.hasNext();) {
400:
401: Namespace ns = (Namespace) i.next();
402: if (ns.getNamespaceURI().equals(nsURI)) {
403:
404: return ns.getPrefix();
405:
406: }
407:
408: }
409:
410: return super .getPrefix(nsURI);
411:
412: }
413:
414: public Iterator getPrefixes(String nsURI) {
415:
416: // lazily-loaded set to store found prefixes
417: List prefixes = null;
418:
419: // add our prefixes first
420: if (namespaces != null) {
421:
422: for (Iterator i = namespaces.values().iterator(); i
423: .hasNext();) {
424:
425: Namespace ns = (Namespace) i.next();
426: if (ns.getNamespaceURI().equals(nsURI)) {
427:
428: if (prefixes == null) {
429:
430: prefixes = new ArrayList();
431:
432: }
433:
434: String prefix = ns.getPrefix();
435: prefixes.add(prefix);
436:
437: }
438:
439: }
440:
441: }
442:
443: // copy parent prefixes that aren't redefined by this context
444: Iterator parentPrefixes = super .getPrefixes(nsURI);
445: while (parentPrefixes.hasNext()) {
446:
447: String prefix = (String) parentPrefixes.next();
448:
449: // only add the prefix if we haven't redefined it
450: if (namespaces != null
451: && !namespaces.containsKey(prefix)) {
452:
453: if (prefixes == null) {
454:
455: prefixes = new ArrayList();
456:
457: }
458: prefixes.add(prefix);
459:
460: }
461:
462: }
463:
464: return prefixes == null ? Collections.EMPTY_LIST.iterator()
465: : prefixes.iterator();
466:
467: }
468:
469: }
470:
471: }
|