001: /*
002: * $Id: StAXStreamWriter.java,v 1.1 2004/07/15 02:15:56 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.io;
035:
036: import java.io.IOException;
037: import java.io.OutputStream;
038: import java.io.OutputStreamWriter;
039: import java.io.UnsupportedEncodingException;
040: import java.io.Writer;
041:
042: import javanet.staxutils.helpers.ElementContext;
043:
044: import javax.xml.namespace.NamespaceContext;
045: import javax.xml.namespace.QName;
046: import javax.xml.stream.XMLStreamException;
047: import javax.xml.stream.XMLStreamWriter;
048:
049: /**
050: * An {@link XMLStreamWriter} implementation that writes to a character stream.
051: *
052: * @author Christian Niles
053: * @version $Revision: 1.1 $
054: */
055: public class StAXStreamWriter implements XMLStreamWriter {
056:
057: /** The destination stream. */
058: private Writer writer;
059:
060: /** Whether the stream has been closed or not. */
061: private boolean closed;
062:
063: /** The root namespace context. */
064: private NamespaceContext rootContext;
065:
066: /** The current {@link ElementContext}. used to keep track of opened elements. */
067: private ElementContext elementContext;
068:
069: /**
070: * Constructs a <code>StAXStreamWriter</code> that writes to the provided
071: * {@link OutputStream} using the platform default encoding.
072: *
073: * @param stream The destination stream.
074: */
075: public StAXStreamWriter(OutputStream stream) {
076:
077: this (new OutputStreamWriter(stream));
078:
079: }
080:
081: /**
082: * Constructs a <code>StAXStreamWriter</code> that writes to the provided
083: * {@link OutputStream} using the specified encoding.
084: *
085: * @param stream The destination stream.
086: * @param encoding The output encoding.
087: * @throws UnsupportedEncodingException If the encoding isn't supported.
088: */
089: public StAXStreamWriter(OutputStream stream, String encoding)
090: throws UnsupportedEncodingException {
091:
092: this (new OutputStreamWriter(stream, encoding));
093:
094: }
095:
096: /**
097: * Constructs a <code>StAXStreamWriter</code> that writes to the provided
098: * {@link Writer}.
099: *
100: * @param writer The destination {@link Writer} instance.
101: */
102: public StAXStreamWriter(Writer writer) {
103:
104: this .writer = writer;
105:
106: }
107:
108: /**
109: * Constructs a <code>StAXStreamWriter</code> that writes to the provided
110: * {@link Writer}.
111: *
112: * @param writer The destination {@link Writer} instance.
113: * @param rootContext The root namespace context.
114: */
115: public StAXStreamWriter(Writer writer, NamespaceContext rootContext) {
116:
117: this .writer = writer;
118: this .rootContext = rootContext;
119:
120: }
121:
122: public synchronized void close() throws XMLStreamException {
123:
124: if (!closed) {
125:
126: flush();
127:
128: closed = true;
129: writer = null;
130:
131: }
132:
133: }
134:
135: public synchronized void flush() throws XMLStreamException {
136:
137: // if cached element info exists, send it
138: closeElementContext();
139:
140: try {
141:
142: writer.flush();
143:
144: } catch (IOException e) {
145:
146: throw new XMLStreamException(e);
147:
148: }
149:
150: }
151:
152: public String getPrefix(String uri) throws XMLStreamException {
153:
154: return getNamespaceContext().getPrefix(uri);
155:
156: }
157:
158: public Object getProperty(String name)
159: throws IllegalArgumentException {
160:
161: // TODO provide access to properties?
162: throw new IllegalArgumentException(name
163: + " property not supported");
164:
165: }
166:
167: public void writeStartDocument() throws XMLStreamException {
168:
169: try {
170:
171: XMLWriterUtils.writeStartDocument(writer);
172:
173: } catch (IOException e) {
174:
175: throw new XMLStreamException(e);
176:
177: }
178:
179: }
180:
181: public void writeStartDocument(String version)
182: throws XMLStreamException {
183:
184: try {
185:
186: XMLWriterUtils.writeStartDocument(version, writer);
187:
188: } catch (IOException e) {
189:
190: throw new XMLStreamException(e);
191:
192: }
193:
194: }
195:
196: public synchronized void writeStartDocument(String encoding,
197: String version) throws XMLStreamException {
198:
199: // TODO perform check that StartDocument can be entered
200: try {
201:
202: XMLWriterUtils
203: .writeStartDocument(version, encoding, writer);
204:
205: } catch (IOException e) {
206:
207: throw new XMLStreamException(e);
208:
209: }
210:
211: }
212:
213: public synchronized void writeEndDocument()
214: throws XMLStreamException {
215:
216: // flush any cached start element content
217: closeElementContext();
218:
219: // close any tags
220: while (elementContext != null) {
221:
222: writeEndElement();
223:
224: }
225:
226: }
227:
228: public synchronized void writeCData(String data)
229: throws XMLStreamException {
230:
231: if (data == null) {
232:
233: throw new IllegalArgumentException(
234: "CDATA argument was null");
235:
236: }
237:
238: // flush any cached start element content
239: closeElementContext();
240:
241: // TODO verify data is appropriate for CDATA
242: try {
243:
244: XMLWriterUtils.writeCData(data, writer);
245:
246: } catch (IOException e) {
247:
248: throw new XMLStreamException(e);
249:
250: }
251:
252: }
253:
254: public synchronized void writeCharacters(char[] text, int start,
255: int len) throws XMLStreamException {
256:
257: if (text == null) {
258:
259: throw new IllegalArgumentException(
260: "Character text argument was null");
261:
262: }
263:
264: // flush any cached start element content
265: closeElementContext();
266:
267: try {
268:
269: XMLWriterUtils.writeCharacters(text, start, len, writer);
270:
271: } catch (IOException e) {
272:
273: throw new XMLStreamException(e);
274:
275: }
276:
277: }
278:
279: public synchronized void writeCharacters(String text)
280: throws XMLStreamException {
281:
282: if (text == null) {
283:
284: throw new IllegalArgumentException(
285: "Character text argument was null");
286:
287: }
288:
289: // flush any cached start element content
290: closeElementContext();
291:
292: try {
293:
294: XMLWriterUtils.writeCharacters(text, writer);
295:
296: } catch (IOException e) {
297:
298: throw new XMLStreamException(e);
299:
300: }
301:
302: }
303:
304: public synchronized void writeComment(String data)
305: throws XMLStreamException {
306:
307: if (data == null) {
308:
309: throw new IllegalArgumentException(
310: "Comment data argument was null");
311:
312: }
313:
314: // flush any cached start element content
315: closeElementContext();
316:
317: // TODO check comment for non-SGML compatible characters?
318: try {
319:
320: XMLWriterUtils.writeComment(data, writer);
321:
322: } catch (IOException e) {
323:
324: throw new XMLStreamException(e);
325:
326: }
327:
328: }
329:
330: public synchronized void writeDTD(String dtd)
331: throws XMLStreamException {
332:
333: if (dtd == null) {
334:
335: throw new IllegalArgumentException("dtd argument was null");
336:
337: }
338:
339: try {
340:
341: XMLWriterUtils.writeDTD(dtd, writer);
342:
343: } catch (IOException e) {
344:
345: throw new XMLStreamException(e);
346:
347: }
348:
349: }
350:
351: public synchronized void writeEntityRef(String name)
352: throws XMLStreamException {
353:
354: // flush any cached start element content
355: closeElementContext();
356:
357: try {
358:
359: XMLWriterUtils.writeEntityReference(name, writer);
360:
361: } catch (IOException e) {
362:
363: throw new XMLStreamException(e);
364:
365: }
366:
367: }
368:
369: public synchronized void writeProcessingInstruction(String target,
370: String data) throws XMLStreamException {
371:
372: // flush any cached start element content
373: closeElementContext();
374:
375: // TODO test processing instruction validity?
376: try {
377:
378: XMLWriterUtils.writeProcessingInstruction(target, data,
379: writer);
380:
381: } catch (IOException e) {
382:
383: throw new XMLStreamException(e);
384:
385: }
386:
387: }
388:
389: public void writeProcessingInstruction(String target)
390: throws XMLStreamException {
391:
392: writeProcessingInstruction(target, null);
393:
394: }
395:
396: public NamespaceContext getNamespaceContext() {
397:
398: return elementContext;
399:
400: }
401:
402: public void setNamespaceContext(NamespaceContext context)
403: throws XMLStreamException {
404:
405: if (this .rootContext == null && elementContext == null) {
406:
407: this .rootContext = context;
408:
409: } else {
410:
411: throw new IllegalStateException(
412: "NamespaceContext has already been set or document is already in progress");
413:
414: }
415:
416: }
417:
418: public synchronized void setDefaultNamespace(String uri)
419: throws XMLStreamException {
420:
421: elementContext.putNamespace("", uri);
422:
423: }
424:
425: public synchronized void setPrefix(String prefix, String uri)
426: throws XMLStreamException {
427:
428: elementContext.putNamespace(prefix, uri);
429:
430: }
431:
432: /**
433: * Core start tag output method called by all other <code>writeXXXElement</code>
434: * methods.
435: *
436: * @param prefix The tag prefix.
437: * @param localName The tag local name.
438: * @param namespaceURI The namespace URI of the prefix.
439: * @param isEmpty Whether the tag is empty.
440: * @throws XMLStreamException If an error occurs writing the tag to the stream.
441: */
442: public synchronized void writeStartElement(String prefix,
443: String localName, String namespaceURI, boolean isEmpty)
444: throws XMLStreamException {
445:
446: if (prefix == null) {
447:
448: throw new IllegalArgumentException(
449: "prefix may not be null @ [" + getCurrentPath()
450: + "]");
451:
452: } else if (localName == null) {
453:
454: throw new IllegalArgumentException(
455: "localName may not be null @ [" + getCurrentPath()
456: + "]");
457:
458: } else if (namespaceURI == null) {
459:
460: throw new IllegalArgumentException(
461: "namespaceURI may not be null @ ["
462: + getCurrentPath() + "]");
463:
464: }
465:
466: // new context is beginning; close the current context if needed
467: if (elementContext != null) {
468:
469: closeElementContext();
470:
471: // test if we just closed an empty root context
472: if (elementContext == null) {
473:
474: throw new XMLStreamException(
475: "Writing start tag after close of root element");
476:
477: }
478:
479: }
480:
481: // create the new context
482: QName name = new QName(namespaceURI, localName, prefix);
483: elementContext = new ElementContext(name, elementContext,
484: isEmpty);
485:
486: }
487:
488: public void writeStartElement(String prefix, String localName,
489: String namespaceURI) throws XMLStreamException {
490:
491: writeStartElement(prefix, localName, namespaceURI, false);
492:
493: }
494:
495: public void writeStartElement(String namespaceURI, String localName)
496: throws XMLStreamException {
497:
498: writeStartElement("", localName, namespaceURI, false);
499:
500: }
501:
502: public void writeStartElement(String localName)
503: throws XMLStreamException {
504:
505: writeStartElement("", localName, "", false);
506:
507: }
508:
509: public void writeEmptyElement(String prefix, String localName,
510: String namespaceURI) throws XMLStreamException {
511:
512: writeStartElement(prefix, localName, namespaceURI, true);
513:
514: }
515:
516: public void writeEmptyElement(String namespaceURI, String localName)
517: throws XMLStreamException {
518:
519: writeStartElement("", localName, namespaceURI, true);
520:
521: }
522:
523: public void writeEmptyElement(String localName)
524: throws XMLStreamException {
525:
526: writeStartElement("", localName, "", true);
527:
528: }
529:
530: public synchronized void writeAttribute(QName name, String value)
531: throws XMLStreamException {
532:
533: if (elementContext == null || elementContext.isReadOnly()) {
534:
535: throw new XMLStreamException(
536: getCurrentPath()
537: + ": attributes must be written directly following a start element.");
538:
539: }
540:
541: elementContext.putAttribute(name, value);
542:
543: }
544:
545: public void writeAttribute(String prefix, String namespaceURI,
546: String localName, String value) throws XMLStreamException {
547:
548: if (prefix == null) {
549:
550: throw new IllegalArgumentException(
551: "attribute prefix may not be null @ ["
552: + getCurrentPath() + "]");
553:
554: } else if (localName == null) {
555:
556: throw new IllegalArgumentException(
557: "attribute localName may not be null @ ["
558: + getCurrentPath() + "]");
559:
560: } else if (namespaceURI == null) {
561:
562: throw new IllegalArgumentException(
563: "attribute namespaceURI may not be null @ ["
564: + getCurrentPath() + "]");
565:
566: }
567:
568: writeAttribute(new QName(namespaceURI, localName, prefix),
569: value);
570:
571: }
572:
573: public void writeAttribute(String namespaceURI, String localName,
574: String value) throws XMLStreamException {
575:
576: writeAttribute("", namespaceURI, localName, value);
577:
578: }
579:
580: public void writeAttribute(String localName, String value)
581: throws XMLStreamException {
582:
583: writeAttribute("", "", localName, value);
584:
585: }
586:
587: public void writeDefaultNamespace(String namespaceURI)
588: throws XMLStreamException {
589:
590: writeNamespace("", namespaceURI);
591:
592: }
593:
594: public synchronized void writeNamespace(String prefix,
595: String namespaceURI) throws XMLStreamException {
596:
597: if (prefix == null) {
598:
599: throw new IllegalArgumentException(
600: "Namespace prefix may not be null @ ["
601: + getCurrentPath() + "]");
602:
603: } else if (namespaceURI == null) {
604:
605: throw new IllegalArgumentException(
606: "Namespace URI may not be null @ ["
607: + getCurrentPath() + "]");
608:
609: }
610:
611: if (elementContext != null && !elementContext.isReadOnly()) {
612:
613: elementContext.putNamespace(prefix, namespaceURI);
614:
615: } else {
616:
617: throw new XMLStreamException(
618: getCurrentPath()
619: + ": Namespaces must be written directly following a start tag");
620: }
621:
622: }
623:
624: public synchronized void writeEndElement()
625: throws XMLStreamException {
626:
627: // flush any cached start element content
628: closeElementContext();
629:
630: if (elementContext != null) {
631:
632: QName name = elementContext.getName();
633: try {
634:
635: XMLWriterUtils.writeEndElement(name, writer);
636:
637: } catch (IOException e) {
638:
639: throw new XMLStreamException(getCurrentPath()
640: + ": Error writing end element to stream", e);
641:
642: }
643:
644: // pop the context
645: elementContext = elementContext.getParentContext();
646:
647: } else {
648:
649: throw new XMLStreamException("Unmatched END_ELEMENT");
650:
651: }
652:
653: }
654:
655: /**
656: * Returns the current position of the writer as a path of {@link QName} strings.
657: *
658: * @return The current position of the writer.
659: */
660: public synchronized String getCurrentPath() {
661:
662: if (elementContext == null) {
663:
664: return "/";
665:
666: } else {
667:
668: return elementContext.getPath();
669:
670: }
671:
672: }
673:
674: /**
675: * Closes the current {@link ElementContext}, writing any cached content and
676: * making it read-only. If the current context is empty, it will be popped and
677: * replaced with its parent context. If no context is open, this method has no
678: * effects.
679: *
680: * @throws XMLStreamException If an error occurs flushing any element content.
681: */
682: protected void closeElementContext() throws XMLStreamException {
683:
684: if (elementContext != null && !elementContext.isReadOnly()) {
685:
686: elementContext.setReadOnly();
687:
688: // it hasn't been closed yet, so write it
689: try {
690:
691: writer.write('<');
692: XMLWriterUtils.writeQName(elementContext.getName(),
693: writer);
694:
695: for (int i = 0, s = elementContext.attributeCount(); i < s; i++) {
696:
697: QName name = elementContext.getAttributeName(i);
698: String value = elementContext.getAttribute(i);
699:
700: writer.write(' ');
701: XMLWriterUtils.writeAttribute(name, value, writer);
702:
703: }
704:
705: for (int i = 0, s = elementContext.namespaceCount(); i < s; i++) {
706:
707: String prefix = elementContext
708: .getNamespacePrefix(i);
709: String uri = elementContext.getNamespaceURI(i);
710:
711: writer.write(' ');
712: XMLWriterUtils.writeNamespace(prefix, uri, writer);
713:
714: }
715:
716: if (elementContext.isEmpty()) {
717:
718: writer.write("/>");
719: elementContext = elementContext.getParentContext();
720:
721: } else {
722:
723: writer.write('>');
724:
725: }
726:
727: } catch (IOException e) {
728:
729: throw new XMLStreamException(getCurrentPath()
730: + ": error writing start tag to stream", e);
731:
732: }
733:
734: }
735:
736: }
737: }
|