001: /*
002: * $Id: ElementContext.java,v 1.1 2004/07/15 02:13:54 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.helpers;
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:
046: import javanet.staxutils.SimpleNamespaceContext;
047:
048: /**
049: * Encapsulates access to contextual element information, such as the element name,
050: * attributes, and namespaces. This class is useful for recording element information
051: * in a stack to keep track of the current element c[position in a document.
052: *
053: * @author Christian Niles
054: * @version $Revision: 1.1 $
055: */
056: public class ElementContext extends SimpleNamespaceContext {
057:
058: /** The element name. */
059: private QName name;
060:
061: /** The encapsulating context. */
062: private ElementContext parent;
063:
064: /** Ordered list of attributes. */
065: private List attributeNames;
066:
067: /** Attribute values, keyed by their names. */
068: private Map attributes;
069:
070: /** Ordered list of namespace prefixes. */
071: private List namespacePrefixes;
072:
073: /** Whether the element is an empty element or not. */
074: private boolean isEmpty;
075:
076: /** Whether the context has been closed for further edits. */
077: private boolean readOnly;
078:
079: /**
080: * Constructs a new <code>ElementContext</code> with the provided name and no
081: * enclosing context.
082: *
083: * @param name The element name.
084: */
085: public ElementContext(QName name) {
086:
087: this .name = name;
088:
089: }
090:
091: /**
092: * Constructs a new <code>ElementContext</code> with the provided name and empty
093: * value, and no enclosing context.
094: *
095: * @param name The element name.
096: * @param isEmpty Whether the element is an empty element or not.
097: */
098: public ElementContext(QName name, boolean isEmpty) {
099:
100: this .name = name;
101: this .isEmpty = isEmpty;
102:
103: }
104:
105: /**
106: * Constructs a new <code>ElementContext</code> with the provided name and
107: * namespace context.
108: *
109: * @param name The element name.
110: * @param context The enclosing namespace context.
111: */
112: public ElementContext(QName name, NamespaceContext context) {
113:
114: super (context);
115: this .name = name;
116:
117: }
118:
119: /**
120: * Constructs a new <code>ElementContext</code> with the provided name and
121: * enclosing context.
122: *
123: * @param name The element name.
124: * @param parent The enclosing element context.
125: */
126: public ElementContext(QName name, ElementContext parent) {
127:
128: super (parent);
129: this .name = name;
130: this .parent = parent;
131:
132: }
133:
134: /**
135: * Constructs a new <code>ElementContext</code> with the provided name and
136: * enclosing context.
137: *
138: * @param name The element name.
139: * @param parent The enclosing element context.
140: * @param isEmpty Whether the element is an empty element or not.
141: */
142: public ElementContext(QName name, ElementContext parent,
143: boolean isEmpty) {
144:
145: super (parent);
146: this .name = name;
147: this .parent = parent;
148: this .isEmpty = isEmpty;
149:
150: }
151:
152: /**
153: * Returns a reference to the enclosing <code>ElementContext</code>.
154: *
155: * @return The enclosing context, or <code>null</code>.
156: */
157: public ElementContext getParentContext() {
158:
159: return parent;
160:
161: }
162:
163: /**
164: * Determines if this context has an enclosing context or not.
165: *
166: * @return <code>true</code> if this context is the root context and has no
167: * enclosing context, <code>false</code> otherwise.
168: */
169: public boolean isRoot() {
170:
171: return parent == null;
172:
173: }
174:
175: /**
176: * Returns the qualified name associated with the context.
177: *
178: * @return The qualified name of the context.
179: */
180: public QName getName() {
181:
182: return name;
183:
184: }
185:
186: /**
187: * Returns the current context path.
188: *
189: * @return A string representing the context path.
190: */
191: public String getPath() {
192:
193: return appendPath(new StringBuffer()).toString();
194:
195: }
196:
197: /**
198: * @see #getPath()
199: */
200: public String toString() {
201:
202: return getPath();
203:
204: }
205:
206: /**
207: * Appends the current context path to a {@link StringBuffer}.
208: *
209: * @param buffer The buffer to which to append the context path.
210: * @return The provided buffer.
211: */
212: public StringBuffer appendPath(StringBuffer buffer) {
213:
214: if (parent != null) {
215:
216: parent.appendPath(buffer);
217:
218: }
219: return buffer.append('/').append(name);
220:
221: }
222:
223: /**
224: * Determines the number of enclosing contexts.
225: *
226: * @return The number of enclosing contexts.
227: */
228: public int getDepth() {
229:
230: if (parent == null) {
231:
232: return 0;
233:
234: } else {
235:
236: return parent.getDepth() + 1;
237:
238: }
239:
240: }
241:
242: /**
243: * Constructs a new child <code>ElementContext</code> with the specified name.
244: *
245: * @param name The name associated with the child context.
246: * @return The newly constructed child context.
247: * @throws IllegalStateException If this context is empty.
248: */
249: public ElementContext newSubContext(QName name) {
250:
251: if (!isEmpty()) {
252:
253: return new ElementContext(name, this );
254:
255: } else {
256:
257: throw new IllegalStateException("ElementContext is empty");
258:
259: }
260:
261: }
262:
263: /**
264: * Constructs a new child <code>ElementContext</code> with the specified name
265: * and empty value.
266: *
267: * @param name The name associated with the child context.
268: * @param isEmpty Whether the child context represents an empty element.
269: * @return The newly constructed child context.
270: * @throws IllegalStateException If this context is empty.
271: */
272: public ElementContext newSubContext(QName name, boolean isEmpty) {
273:
274: if (!isEmpty()) {
275:
276: return new ElementContext(name, this , isEmpty);
277:
278: } else {
279:
280: throw new IllegalStateException("ElementContext is empty");
281:
282: }
283:
284: }
285:
286: /**
287: * Adds an attribute to the context with the specified name and value.
288: *
289: * @param name The attribute name.
290: * @param value The attribute value.
291: * @throws IllegalStateException If the context is read-only.
292: */
293: public void putAttribute(QName name, String value) {
294:
295: if (isReadOnly()) {
296:
297: throw new IllegalStateException(
298: "ElementContext is readOnly");
299:
300: } else if (attributes == null) {
301:
302: attributes = new HashMap();
303: attributeNames = new ArrayList();
304:
305: }
306:
307: attributeNames.add(name);
308: attributes.put(name, value);
309:
310: }
311:
312: /**
313: * Adds a namespace declaration to this context with the specified prefix and
314: * namespace uri.
315: *
316: * @param prefix The namespace prefix.
317: * @param nsURI The namespace uri.
318: */
319: public void putNamespace(String prefix, String nsURI) {
320:
321: if (isReadOnly()) {
322:
323: throw new IllegalStateException(
324: "ElementContext is readOnly");
325:
326: }
327:
328: if (namespacePrefixes == null) {
329:
330: namespacePrefixes = new ArrayList();
331:
332: }
333:
334: if (prefix.length() == 0) {
335:
336: // default namespace
337: namespacePrefixes.add(prefix);
338: super .setDefaultNamespace(nsURI);
339:
340: } else {
341:
342: namespacePrefixes.add(prefix);
343: super .setPrefix(prefix, nsURI);
344:
345: }
346:
347: }
348:
349: /**
350: * Returns the number of attributes defined in this context.
351: *
352: * @return The number of attributes defined in the context.
353: */
354: public int attributeCount() {
355:
356: if (attributes != null) {
357:
358: return attributes.size();
359:
360: } else {
361:
362: return 0;
363:
364: }
365:
366: }
367:
368: /**
369: * Returns the value of the <code>idx</code><sup>th</sup> attribute defined on
370: * the context.
371: *
372: * @param idx The zero-based index of the attribute value to retrieve.
373: * @return The value of the <code>idx</code><sup>th</sup> attribute defined on
374: * the context.
375: * @throws IndexOutOfBoundsException If the index is out of bounds.
376: */
377: public String getAttribute(int idx) {
378:
379: return getAttribute(getAttributeName(idx));
380:
381: }
382:
383: /**
384: * Returns the name of the <code>idx</code><sup>th</sup> attribute defined on
385: * the context.
386: *
387: * @param idx The zero-based index of the attribute name to retrieve.
388: * @return The name of the <code>idx</code><sup>th</sup> attribute defined on
389: * the context.
390: * @throws IndexOutOfBoundsException If the index is out of bounds.
391: */
392: public QName getAttributeName(int idx) {
393:
394: if (attributeNames != null) {
395:
396: return (QName) attributeNames.get(idx);
397:
398: } else {
399:
400: throw new IndexOutOfBoundsException("Attribute index "
401: + idx + " doesn't exist");
402:
403: }
404:
405: }
406:
407: /**
408: * Returns the value of a named attribute.
409: *
410: * @param name The name of the attribute value to retrieve.
411: * @return The value of the named attribute, or <code>null</code>.
412: */
413: public String getAttribute(QName name) {
414:
415: if (attributes != null) {
416:
417: return (String) attributes.get(name);
418:
419: } else {
420:
421: return null;
422:
423: }
424:
425: }
426:
427: /**
428: * Determines if an attribute with the specified name exists in this context.
429: *
430: * @param name The name of the attribute.
431: * @return <code>true</code> if an attribute with the specified name has been
432: * defined in this context, <code>false</code> otherwise.
433: */
434: public boolean attributeExists(QName name) {
435:
436: if (attributes != null) {
437:
438: return attributes.containsKey(name);
439:
440: } else {
441:
442: return false;
443:
444: }
445:
446: }
447:
448: /**
449: * Returns an {@link Iterator} over the names of all attributes defined in this
450: * context. The returned iterator will not support the {@link Iterator#remove()}
451: * operation.
452: *
453: * @return An {@link Iterator} over the names of all attributes defined in this
454: * context.
455: */
456: public Iterator attributeNames() {
457:
458: if (attributeNames != null) {
459:
460: return Collections.unmodifiableList(attributeNames)
461: .iterator();
462:
463: } else {
464:
465: return Collections.EMPTY_LIST.iterator();
466:
467: }
468:
469: }
470:
471: /**
472: * Determines the number of namespaces declared in this context.
473: *
474: * @return The number of namespaces declared in this context.
475: */
476: public int namespaceCount() {
477:
478: if (namespacePrefixes != null) {
479:
480: return namespacePrefixes.size();
481:
482: } else {
483:
484: return 0;
485:
486: }
487:
488: }
489:
490: /**
491: * Returns the URI of the <code>idx</code><sup>th</sup> namespace declaration
492: * defined in this context.
493: *
494: * @param idx The index of the namespace URI to return.
495: * @return The URI of the <code>idx</code><sup>th</sup> namespace declaration
496: * defined in this context.
497: * @throws IndexOutOfBoundsException If the index is out of bounds.
498: */
499: public String getNamespaceURI(int idx) {
500:
501: return this .getNamespaceURI(getNamespacePrefix(idx));
502:
503: }
504:
505: /**
506: * Returns the prefix of the <code>idx</code><sup>th</sup> namespace declaration
507: * defined in this context.
508: *
509: * @param idx The index of the namespace prefix to return.
510: * @return The prefix of the <code>idx</code><sup>th</sup> namespace declaration
511: * defined in this context.
512: * @throws IndexOutOfBoundsException If the index is out of bounds.
513: */
514: public String getNamespacePrefix(int idx) {
515:
516: if (namespacePrefixes != null) {
517:
518: return (String) namespacePrefixes.get(idx);
519:
520: } else {
521:
522: throw new IndexOutOfBoundsException("Namespace index "
523: + idx + " doesn't exist");
524:
525: }
526:
527: }
528:
529: /**
530: * Whether this context may be edited or not.
531: *
532: * @return <code>true</code> if no additional modifications may be made to this
533: * context, <code>false</code> otherwise.
534: */
535: public boolean isReadOnly() {
536:
537: return readOnly;
538:
539: }
540:
541: /**
542: * Prevents any further additions to this context.
543: */
544: public void setReadOnly() {
545:
546: this .readOnly = true;
547:
548: }
549:
550: /**
551: * Whether this context represents an emtpy element. Empty contexts may not
552: * enclose any other contexts.
553: *
554: * @return <code>true</code> if this context represents an emtpy element,
555: * <code>false</code> otherwise.
556: */
557: public boolean isEmpty() {
558:
559: return isEmpty;
560:
561: }
562:
563: }
|