1 /*
2 * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation. Sun designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Sun in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
22 * CA 95054 USA or visit www.sun.com if you need additional information or
23 * have any questions.
24 *
25 * THIS FILE WAS MODIFIED BY SUN MICROSYSTEMS, INC.
26 */
27
28 /*
29 * Copyright 2006 Sun Microsystems, Inc. All Rights Reserved.
30 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
31 *
32 * This code is free software; you can redistribute it and/or modify it
33 * under the terms of the GNU General Public License version 2 only, as
34 * published by the Free Software Foundation. Sun designates this
35 * particular file as subject to the "Classpath" exception as provided
36 * by Sun in the LICENSE file that accompanied this code.
37 *
38 * This code is distributed in the hope that it will be useful, but WITHOUT
39 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
40 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
41 * version 2 for more details (a copy is included in the LICENSE file that
42 * accompanied this code).
43 *
44 * You should have received a copy of the GNU General Public License version
45 * 2 along with this work; if not, write to the Free Software Foundation,
46 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
47 *
48 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
49 * CA 95054 USA or visit www.sun.com if you need additional information or
50 * have any questions.
51 *
52 * THIS FILE WAS MODIFIED BY SUN MICROSYSTEMS, INC.
53 *
54 */
55
56 package com.sun.xml.internal.fastinfoset.stax;
57
58 import com.sun.xml.internal.fastinfoset.Encoder;
59 import com.sun.xml.internal.fastinfoset.EncodingConstants;
60 import java.io.IOException;
61 import java.io.OutputStream;
62 import java.util.EmptyStackException;
63 import java.util.Enumeration;
64 import java.util.Iterator;
65 import javax.xml.namespace.NamespaceContext;
66 import javax.xml.stream.XMLStreamException;
67 import javax.xml.stream.XMLStreamWriter;
68 import com.sun.xml.internal.org.jvnet.fastinfoset.EncodingAlgorithmIndexes;
69 import org.xml.sax.helpers.NamespaceSupport;
70 import com.sun.xml.internal.fastinfoset.CommonResourceBundle;
71
72 /**
73 * The Fast Infoset StAX serializer.
74 * <p>
75 * Instantiate this serializer to serialize a fast infoset document in accordance
76 * with the StAX API.
77 *
78 * <p>
79 * More than one fast infoset document may be encoded to the
80 * {@link java.io.OutputStream}.
81 */
82 public class StAXDocumentSerializer extends Encoder implements XMLStreamWriter {
83 protected StAXManager _manager;
84
85 protected String _encoding;
86 /**
87 * Local name of current element.
88 */
89 protected String _currentLocalName;
90
91 /**
92 * Namespace of current element.
93 */
94 protected String _currentUri;
95
96 /**
97 * Prefix of current element.
98 */
99 protected String _currentPrefix;
100
101 /**
102 * This flag indicates when there is a pending start element event.
103 */
104 protected boolean _inStartElement = false;
105
106 /**
107 * This flag indicates if the current element is empty.
108 */
109 protected boolean _isEmptyElement = false;
110
111 /**
112 * List of attributes qnames and values defined in the current element.
113 */
114 protected String[] _attributesArray = new String[4 * 16];
115 protected int _attributesArrayIndex = 0;
116
117 /**
118 * Mapping between uris and prefixes.
119 */
120 protected NamespaceSupport _nsSupport = new NamespaceSupport();
121
122 protected boolean[] _nsSupportContextStack = new boolean[32];
123 protected int _stackCount = -1;
124
125 protected NamespaceContext _nsContext = new NamespaceContextImpl();
126
127 /**
128 * List of namespaces defined in the current element.
129 */
130 protected String[] _namespacesArray = new String[2 * 8];
131 protected int _namespacesArrayIndex = 0;
132
133 public StAXDocumentSerializer() {
134 super(true);
135 }
136
137 public StAXDocumentSerializer(OutputStream outputStream) {
138 super(true);
139 setOutputStream(outputStream);
140 }
141
142 public StAXDocumentSerializer(OutputStream outputStream, StAXManager manager) {
143 super(true);
144 setOutputStream(outputStream);
145 _manager = manager;
146 }
147
148 public void reset() {
149 super.reset();
150
151 _attributesArrayIndex = 0;
152 _namespacesArrayIndex = 0;
153 _nsSupport.reset();
154 _stackCount = -1;
155
156 _currentUri = _currentPrefix = null;
157 _currentLocalName = null;
158
159 _inStartElement = _isEmptyElement = false;
160 }
161
162 // -- XMLStreamWriter Interface -------------------------------------------
163
164 public void writeStartDocument() throws XMLStreamException {
165 writeStartDocument("finf", "1.0");
166 }
167
168 public void writeStartDocument(String version) throws XMLStreamException {
169 writeStartDocument("finf", version);
170 }
171
172 public void writeStartDocument(String encoding, String version)
173 throws XMLStreamException
174 {
175 reset();
176
177 try {
178 encodeHeader(false);
179 encodeInitialVocabulary();
180 } catch (IOException e) {
181 throw new XMLStreamException(e);
182 }
183 }
184
185 public void writeEndDocument() throws XMLStreamException {
186 // Need to flush a pending empty element?
187 if (_inStartElement) {
188 // encodeTerminationAndCurrentElement();
189 }
190
191 try {
192 // TODO
193 // Use nsSupport to terminate all elements not terminated
194 // by writeEndElement
195
196
197 encodeDocumentTermination();
198 }
199 catch (IOException e) {
200 throw new XMLStreamException(e);
201 }
202 }
203
204 public void close() throws XMLStreamException {
205 reset();
206 }
207
208 public void flush() throws XMLStreamException {
209 try {
210 _s.flush();
211 }
212 catch (IOException e) {
213 throw new XMLStreamException(e);
214 }
215 }
216
217 public void writeStartElement(String localName)
218 throws XMLStreamException
219 {
220 // TODO is it necessary for FI to obtain the default namespace in scope?
221 writeStartElement("", localName, "");
222 }
223
224 public void writeStartElement(String namespaceURI, String localName)
225 throws XMLStreamException
226 {
227 writeStartElement(getPrefix(namespaceURI), localName, namespaceURI);
228 }
229
230 public void writeStartElement(String prefix, String localName,
231 String namespaceURI) throws XMLStreamException
232 {
233 encodeTerminationAndCurrentElement(false);
234
235 _inStartElement = true;
236 _isEmptyElement = false;
237
238 _currentLocalName = localName;
239 _currentPrefix = prefix;
240 _currentUri = namespaceURI;
241
242 _stackCount++;
243 if (_stackCount == _nsSupportContextStack.length) {
244 boolean[] nsSupportContextStack = new boolean[_stackCount * 2];
245 System.arraycopy(_nsSupportContextStack, 0, nsSupportContextStack, 0, _nsSupportContextStack.length);
246 _nsSupportContextStack = nsSupportContextStack;
247 }
248
249 _nsSupportContextStack[_stackCount] = false;
250 // _nsSupport.pushContext();
251 }
252
253 public void writeEmptyElement(String localName)
254 throws XMLStreamException
255 {
256 writeEmptyElement("", localName, "");
257 }
258
259 public void writeEmptyElement(String namespaceURI, String localName)
260 throws XMLStreamException
261 {
262 writeEmptyElement(getPrefix(namespaceURI), localName, namespaceURI);
263 }
264
265 public void writeEmptyElement(String prefix, String localName,
266 String namespaceURI) throws XMLStreamException
267 {
268 encodeTerminationAndCurrentElement(false);
269
270 _isEmptyElement = _inStartElement = true;
271
272 _currentLocalName = localName;
273 _currentPrefix = prefix;
274 _currentUri = namespaceURI;
275
276 _stackCount++;
277 if (_stackCount == _nsSupportContextStack.length) {
278 boolean[] nsSupportContextStack = new boolean[_stackCount * 2];
279 System.arraycopy(_nsSupportContextStack, 0, nsSupportContextStack, 0, _nsSupportContextStack.length);
280 _nsSupportContextStack = nsSupportContextStack;
281 }
282
283 _nsSupportContextStack[_stackCount] = false;
284 //_nsSupport.pushContext();
285 }
286
287 public void writeEndElement() throws XMLStreamException {
288 if (_inStartElement) {
289 encodeTerminationAndCurrentElement(false);
290 }
291
292 try {
293 encodeElementTermination();
294 if (_nsSupportContextStack[_stackCount--] == true) {
295 _nsSupport.popContext();
296 }
297 }
298 catch (IOException e) {
299 throw new XMLStreamException(e);
300 }
301 catch (EmptyStackException e) {
302 throw new XMLStreamException(e);
303 }
304 }
305
306
307 public void writeAttribute(String localName, String value)
308 throws XMLStreamException
309 {
310 writeAttribute("", "", localName, value);
311 }
312
313 public void writeAttribute(String namespaceURI, String localName,
314 String value) throws XMLStreamException
315 {
316 String prefix = "";
317
318 // Find prefix for attribute, ignoring default namespace
319 if (namespaceURI.length() > 0) {
320 prefix = _nsSupport.getPrefix(namespaceURI);
321
322 // Undeclared prefix or ignorable default ns?
323 if (prefix == null || prefix.length() == 0) {
324 // Workaround for BUG in SAX NamespaceSupport helper
325 // which incorrectly defines namespace declaration URI
326 if (namespaceURI == EncodingConstants.XMLNS_NAMESPACE_NAME ||
327 namespaceURI.equals(EncodingConstants.XMLNS_NAMESPACE_NAME)) {
328 // TODO
329 // Need to check carefully the rule for the writing of
330 // namespaces in StAX. Is it safe to ignore such
331 // attributes, as declarations will be made using the
332 // writeNamespace method
333 return;
334 }
335 throw new XMLStreamException(CommonResourceBundle.getInstance().getString("message.URIUnbound", new Object[]{namespaceURI}));
336 }
337 }
338 writeAttribute(prefix, namespaceURI, localName, value);
339 }
340
341 public void writeAttribute(String prefix, String namespaceURI,
342 String localName, String value) throws XMLStreamException
343 {
344 if (!_inStartElement) {
345 throw new IllegalStateException(CommonResourceBundle.getInstance().getString("message.attributeWritingNotAllowed"));
346 }
347
348 // TODO
349 // Need to check carefully the rule for the writing of
350 // namespaces in StAX. Is it safe to ignore such
351 // attributes, as declarations will be made using the
352 // writeNamespace method
353 if (namespaceURI == EncodingConstants.XMLNS_NAMESPACE_NAME ||
354 namespaceURI.equals(EncodingConstants.XMLNS_NAMESPACE_NAME)) {
355 return;
356 }
357
358 if (_attributesArrayIndex == _attributesArray.length) {
359 final String[] attributesArray = new String[_attributesArrayIndex * 2];
360 System.arraycopy(_attributesArray, 0, attributesArray, 0, _attributesArrayIndex);
361 _attributesArray = attributesArray;
362 }
363
364 _attributesArray[_attributesArrayIndex++] = namespaceURI;
365 _attributesArray[_attributesArrayIndex++] = prefix;
366 _attributesArray[_attributesArrayIndex++] = localName;
367 _attributesArray[_attributesArrayIndex++] = value;
368 }
369
370 public void writeNamespace(String prefix, String namespaceURI)
371 throws XMLStreamException
372 {
373 if (prefix == null || prefix.length() == 0 || prefix.equals(EncodingConstants.XMLNS_NAMESPACE_PREFIX)) {
374 writeDefaultNamespace(namespaceURI);
375 }
376 else {
377 if (!_inStartElement) {
378 throw new IllegalStateException(CommonResourceBundle.getInstance().getString("message.attributeWritingNotAllowed"));
379 }
380
381 if (_namespacesArrayIndex == _namespacesArray.length) {
382 final String[] namespacesArray = new String[_namespacesArrayIndex * 2];
383 System.arraycopy(_namespacesArray, 0, namespacesArray, 0, _namespacesArrayIndex);
384 _namespacesArray = namespacesArray;
385 }
386
387 _namespacesArray[_namespacesArrayIndex++] = prefix;
388 _namespacesArray[_namespacesArrayIndex++] = namespaceURI;
389 }
390 }
391
392 public void writeDefaultNamespace(String namespaceURI)
393 throws XMLStreamException
394 {
395 if (!_inStartElement) {
396 throw new IllegalStateException(CommonResourceBundle.getInstance().getString("message.attributeWritingNotAllowed"));
397 }
398
399 if (_namespacesArrayIndex == _namespacesArray.length) {
400 final String[] namespacesArray = new String[_namespacesArrayIndex * 2];
401 System.arraycopy(_namespacesArray, 0, namespacesArray, 0, _namespacesArrayIndex);
402 _namespacesArray = namespacesArray;
403 }
404
405 _namespacesArray[_namespacesArrayIndex++] = "";
406 _namespacesArray[_namespacesArrayIndex++] = namespaceURI;
407 }
408
409 public void writeComment(String data) throws XMLStreamException {
410 try {
411 if (getIgnoreComments()) return;
412
413 encodeTerminationAndCurrentElement(true);
414
415 // TODO: avoid array copy here
416 encodeComment(data.toCharArray(), 0, data.length());
417 }
418 catch (IOException e) {
419 throw new XMLStreamException(e);
420 }
421 }
422
423 public void writeProcessingInstruction(String target)
424 throws XMLStreamException
425 {
426 writeProcessingInstruction(target, "");
427 }
428
429 public void writeProcessingInstruction(String target, String data)
430 throws XMLStreamException
431 {
432 try {
433 if (getIgnoreProcesingInstructions()) return;
434
435 encodeTerminationAndCurrentElement(true);
436
437 encodeProcessingInstruction(target, data);
438 }
439 catch (IOException e) {
440 throw new XMLStreamException(e);
441 }
442 }
443
444 public void writeCData(String data) throws XMLStreamException {
445 throw new UnsupportedOperationException(CommonResourceBundle.getInstance().getString("message.notImplemented"));
446 }
447
448 public void writeDTD(String dtd) throws XMLStreamException {
449 throw new UnsupportedOperationException(CommonResourceBundle.getInstance().getString("message.notImplemented"));
450 }
451
452 public void writeEntityRef(String name) throws XMLStreamException {
453 throw new UnsupportedOperationException(CommonResourceBundle.getInstance().getString("message.notImplemented"));
454 }
455
456 public void writeCharacters(String text) throws XMLStreamException {
457 try {
458 final int length = text.length();
459 if (length == 0) {
460 return;
461 } else if (length < _charBuffer.length) {
462 if (getIgnoreWhiteSpaceTextContent() &&
463 isWhiteSpace(text)) return;
464
465 // Warning: this method must be called before any state
466 // is modified, such as the _charBuffer contents,
467 // so the characters of text cannot be copied to _charBuffer
468 // before this call
469 encodeTerminationAndCurrentElement(true);
470
471 text.getChars(0, length, _charBuffer, 0);
472 encodeCharacters(_charBuffer, 0, length);
473 } else {
474 final char ch[] = text.toCharArray();
475 if (getIgnoreWhiteSpaceTextContent() &&
476 isWhiteSpace(ch, 0, length)) return;
477
478 encodeTerminationAndCurrentElement(true);
479
480 encodeCharactersNoClone(ch, 0, length);
481 }
482 }
483 catch (IOException e) {
484 throw new XMLStreamException(e);
485 }
486 }
487
488 public void writeCharacters(char[] text, int start, int len)
489 throws XMLStreamException
490 {
491 try {
492 if (len <= 0) {
493 return;
494 }
495
496 if (getIgnoreWhiteSpaceTextContent() &&
497 isWhiteSpace(text, start, len)) return;
498
499 encodeTerminationAndCurrentElement(true);
500
501 encodeCharacters(text, start, len);
502 }
503 catch (IOException e) {
504 throw new XMLStreamException(e);
505 }
506 }
507
508 public String getPrefix(String uri) throws XMLStreamException {
509 return _nsSupport.getPrefix(uri);
510 }
511
512 public void setPrefix(String prefix, String uri)
513 throws XMLStreamException
514 {
515 if (_stackCount > -1 && _nsSupportContextStack[_stackCount] == false) {
516 _nsSupportContextStack[_stackCount] = true;
517 _nsSupport.pushContext();
518 }
519
520 _nsSupport.declarePrefix(prefix, uri);
521 }
522
523 public void setDefaultNamespace(String uri) throws XMLStreamException {
524 setPrefix("", uri);
525 }
526
527 /**
528 * Sets the current namespace context for prefix and uri bindings.
529 * This context becomes the root namespace context for writing and
530 * will replace the current root namespace context. Subsequent calls
531 * to setPrefix and setDefaultNamespace will bind namespaces using
532 * the context passed to the method as the root context for resolving
533 * namespaces. This method may only be called once at the start of
534 * the document. It does not cause the namespaces to be declared.
535 * If a namespace URI to prefix mapping is found in the namespace
536 * context it is treated as declared and the prefix may be used
537 * by the StreamWriter.
538 * @param context the namespace context to use for this writer, may not be null
539 * @throws XMLStreamException
540 */
541 public void setNamespaceContext(NamespaceContext context)
542 throws XMLStreamException
543 {
544 throw new UnsupportedOperationException("setNamespaceContext");
545 }
546
547 public NamespaceContext getNamespaceContext() {
548 return _nsContext;
549 }
550
551 public Object getProperty(java.lang.String name)
552 throws IllegalArgumentException
553 {
554 if (_manager != null) {
555 return _manager.getProperty(name);
556 }
557 return null;
558 }
559
560 public void setManager(StAXManager manager) {
561 _manager = manager;
562 }
563
564 public void setEncoding(String encoding) {
565 _encoding = encoding;
566 }
567
568 protected class NamespaceContextImpl implements NamespaceContext {
569 public final String getNamespaceURI(String prefix) {
570 return _nsSupport.getURI(prefix);
571 }
572
573 public final String getPrefix(String namespaceURI) {
574 return _nsSupport.getPrefix(namespaceURI);
575 }
576
577 public final Iterator getPrefixes(String namespaceURI) {
578 final Enumeration e = _nsSupport.getPrefixes(namespaceURI);
579
580 return new Iterator() {
581 public boolean hasNext() {
582 return e.hasMoreElements();
583 }
584
585 public Object next() {
586 return e.nextElement();
587 }
588
589 public void remove() {
590 throw new UnsupportedOperationException();
591 }
592 };
593 }
594 }
595
596 public void writeOctets(byte[] b, int start, int len)
597 throws XMLStreamException
598 {
599 try {
600 if (len == 0) {
601 return;
602 }
603
604 encodeTerminationAndCurrentElement(true);
605
606 encodeCIIOctetAlgorithmData(EncodingAlgorithmIndexes.BASE64, b, start, len);
607 }
608 catch (IOException e) {
609 throw new XMLStreamException(e);
610 }
611 }
612
613 protected void encodeTerminationAndCurrentElement(boolean terminateAfter) throws XMLStreamException {
614 try {
615 encodeTermination();
616
617 if (_inStartElement) {
618
619 _b = EncodingConstants.ELEMENT;
620 if (_attributesArrayIndex > 0) {
621 _b |= EncodingConstants.ELEMENT_ATTRIBUTE_FLAG;
622 }
623
624 // Encode namespace decls associated with this element
625 if (_namespacesArrayIndex > 0) {
626 write(_b | EncodingConstants.ELEMENT_NAMESPACES_FLAG);
627 for (int i = 0; i < _namespacesArrayIndex;) {
628 encodeNamespaceAttribute(_namespacesArray[i++], _namespacesArray[i++]);
629 }
630 _namespacesArrayIndex = 0;
631
632 write(EncodingConstants.TERMINATOR);
633
634 _b = 0;
635 }
636
637 // Encode element and its attributes
638 encodeElementQualifiedNameOnThirdBit(_currentUri, _currentPrefix, _currentLocalName);
639
640 for (int i = 0; i < _attributesArrayIndex;) {
641 encodeAttributeQualifiedNameOnSecondBit(
642 _attributesArray[i++], _attributesArray[i++], _attributesArray[i++]);
643
644 final String value = _attributesArray[i];
645 _attributesArray[i++] = null;
646 final boolean addToTable = (value.length() < attributeValueSizeConstraint) ? true : false;
647 encodeNonIdentifyingStringOnFirstBit(value, _v.attributeValue, addToTable);
648
649 _b = EncodingConstants.TERMINATOR;
650 _terminate = true;
651 }
652 _attributesArrayIndex = 0;
653 _inStartElement = false;
654
655 if (_isEmptyElement) {
656 encodeElementTermination();
657 if (_nsSupportContextStack[_stackCount--] == true) {
658 _nsSupport.popContext();
659 }
660
661 _isEmptyElement = false;
662 }
663
664 if (terminateAfter) {
665 encodeTermination();
666 }
667 }
668 } catch (IOException e) {
669 throw new XMLStreamException(e);
670 }
671 }
672 }