001: /*
002: Copyright (c) 2004, Dennis M. Sosnoski.
003: All rights reserved.
004:
005: Redistribution and use in source and binary forms, with or without modification,
006: are permitted provided that the following conditions are met:
007:
008: * Redistributions of source code must retain the above copyright notice, this
009: list of conditions and the following disclaimer.
010: * Redistributions in binary form must reproduce the above copyright notice,
011: this list of conditions and the following disclaimer in the documentation
012: and/or other materials provided with the distribution.
013: * Neither the name of JiBX nor the names of its contributors may be used
014: to endorse or promote products derived from this software without specific
015: prior written permission.
016:
017: THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
018: ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
019: WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
020: DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
021: ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
022: (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
023: LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
024: ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
025: (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
026: SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
027: */
028:
029: package org.jibx.runtime.impl;
030:
031: import java.io.IOException;
032: import java.io.OutputStream;
033: import java.io.UnsupportedEncodingException;
034:
035: /**
036: * Base handler for marshalling text document to an output stream. This is
037: * designed for use with character encodings that use standard one-byte values
038: * for Unicode characters in the 0x20-0x7F range, which includes the most
039: * widely used encodings for XML documents. It needs to be subclassed with
040: * implementation methods specific to the encoding used.
041: *
042: * @author Dennis M. Sosnoski
043: * @version 1.0
044: */
045: public abstract class StreamWriterBase extends XMLWriterBase {
046: //
047: // Defined entities and special sequences as bytes
048:
049: protected static final byte[] QUOT_ENTITY = { (byte) '&',
050: (byte) 'q', (byte) 'u', (byte) 'o', (byte) 't', (byte) ';' };
051: protected static final byte[] AMP_ENTITY = { (byte) '&',
052: (byte) 'a', (byte) 'm', (byte) 'p', (byte) ';' };
053: protected static final byte[] GT_ENTITY = { (byte) '&', (byte) 'g',
054: (byte) 't', (byte) ';' };
055: protected static final byte[] LT_ENTITY = { (byte) '&', (byte) 'l',
056: (byte) 't', (byte) ';' };
057: protected static final byte[] LT_CDATASTART = { (byte) '<',
058: (byte) '!', (byte) '[', (byte) 'C', (byte) 'D', (byte) 'A',
059: (byte) 'T', (byte) 'A', (byte) '[' };
060: protected static final byte[] LT_CDATAEND = { (byte) ']',
061: (byte) ']', (byte) '>' };
062:
063: /** Default output buffer size. */
064: private static final int INITIAL_BUFFER_SIZE = 2048;
065:
066: /** Name of encoding used for stream. */
067: private final String m_encodingName;
068:
069: /** Stream for text output. */
070: private OutputStream m_stream;
071:
072: /** Buffer for accumulating output bytes. */
073: protected byte[] m_buffer;
074:
075: /** Current offset in filling buffer. */
076: protected int m_fillOffset;
077:
078: /** Byte sequences for prefixes of namespaces in scope. */
079: protected byte[][] m_prefixBytes;
080:
081: /** Byte sequences for prefixes of extension namespaces in scope. */
082: protected byte[][][] m_extensionBytes;
083:
084: /** Indent tags for pretty-printed text. */
085: private boolean m_indent;
086:
087: /** Base number of characters in indent sequence (end of line only). */
088: private int m_indentBase;
089:
090: /** Number of extra characters in indent sequence per level of nesting. */
091: private int m_indentPerLevel;
092:
093: /** Raw text for indentation sequences. */
094: private byte[] m_indentSequence;
095:
096: /**
097: * Constructor.
098: *
099: * @param enc character encoding used for output to streams
100: * @param uris ordered array of URIs for namespaces used in document (must
101: * be constant; the value in position 0 must always be the empty string "",
102: * and the value in position 1 must always be the XML namespace
103: * "http://www.w3.org/XML/1998/namespace")
104: */
105: public StreamWriterBase(String enc, String[] uris) {
106: super (uris);
107: m_encodingName = enc;
108: m_prefixBytes = new byte[uris.length][];
109: m_buffer = new byte[INITIAL_BUFFER_SIZE];
110: }
111:
112: /**
113: * Copy constructor. This takes the stream and encoding information from a
114: * supplied instance, while setting a new array of namespace URIs. It's
115: * intended for use when invoking one binding from within another binding.
116: *
117: * @param base instance to be used as base for writer
118: * @param uris ordered array of URIs for namespaces used in document
119: * (see {@link #StreamWriterBase(String, String[])})
120: */
121: public StreamWriterBase(StreamWriterBase base, String[] uris) {
122: super (base, uris);
123: m_encodingName = base.m_encodingName;
124: m_prefixBytes = new byte[uris.length][];
125: m_buffer = new byte[INITIAL_BUFFER_SIZE];
126: m_stream = base.m_stream;
127: m_indent = base.m_indent;
128: m_indentBase = base.m_indentBase;
129: m_indentPerLevel = base.m_indentPerLevel;
130: m_indentSequence = base.m_indentSequence;
131: m_extensionBytes = new byte[base.m_extensionBytes.length][][];
132: System.arraycopy(base.m_extensionBytes, 0, m_extensionBytes, 0,
133: m_extensionBytes.length);
134: }
135:
136: /**
137: * Set output stream. If an output stream is currently open when this is
138: * called the existing stream is flushed and closed, with any errors
139: * ignored.
140: *
141: * @param outs stream for document data output
142: */
143: public void setOutput(OutputStream outs) {
144: try {
145: close();
146: } catch (IOException e) { /* deliberately empty */
147: }
148: m_stream = outs;
149: reset();
150: }
151:
152: /**
153: * Set nesting indentation. This is advisory only, and implementations of
154: * this interface are free to ignore it. The intent is to indicate that the
155: * generated output should use indenting to illustrate element nesting.
156: *
157: * @param count number of character to indent per level, or disable
158: * indentation if negative (zero means new line only)
159: * @param newline sequence of characters used for a line ending
160: * (<code>null</code> means use the single character '\n')
161: * @param indent whitespace character used for indentation
162: */
163: public void setIndentSpaces(int count, String newline, char indent) {
164: if (count >= 0) {
165: try {
166: if (newline == null) {
167: newline = "\n";
168: }
169: m_indent = true;
170: byte[] base = newline.getBytes(m_encodingName);
171: m_indentBase = base.length;
172: byte[] per = new String(new char[] { indent })
173: .getBytes(m_encodingName);
174: m_indentPerLevel = count * per.length;
175: int length = m_indentBase + m_indentPerLevel * 10;
176: m_indentSequence = new byte[length];
177: for (int i = 0; i < length; i++) {
178: if (i < newline.length()) {
179: m_indentSequence[i] = base[i];
180: } else {
181: int index = (i - m_indentBase) % per.length;
182: m_indentSequence[i] = per[index];
183: }
184: }
185: } catch (UnsupportedEncodingException e) {
186: throw new RuntimeException("Encoding " + m_encodingName
187: + " not recognized by JVM");
188: }
189: } else {
190: m_indent = false;
191: }
192: }
193:
194: /**
195: * Make at least the requested number of bytes available in the output
196: * buffer. If necessary, the output buffer will be replaced by a larger
197: * buffer.
198: *
199: * @param length number of bytes space to be made available
200: * @throws IOException if error writing to document
201: */
202: protected void makeSpace(int length) throws IOException {
203: if (m_fillOffset + length > m_buffer.length) {
204: m_stream.write(m_buffer, 0, m_fillOffset);
205: m_fillOffset = 0;
206: if (length > m_buffer.length) {
207: m_buffer = new byte[Math.max(length,
208: m_buffer.length * 2)];
209: }
210: }
211: }
212:
213: /**
214: * Report that namespace has been undefined.
215: *
216: * @param index namespace URI index number
217: */
218: protected void undefineNamespace(int index) {
219: if (index < m_prefixBytes.length) {
220: m_prefixBytes[index] = null;
221: } else if (m_extensionBytes != null) {
222: index -= m_prefixes.length;
223: for (int i = 0; i < m_extensionBytes.length; i++) {
224: int length = m_extensionBytes[i].length;
225: if (index < length) {
226: m_extensionBytes[i][index] = null;
227: break;
228: } else {
229: index -= length;
230: }
231: }
232: } else {
233: throw new IllegalArgumentException("Index out of range");
234: }
235: }
236:
237: /**
238: * Write namespace prefix to output. This internal method is used to throw
239: * an exception when an undeclared prefix is used.
240: *
241: * @param index namespace URI index number
242: * @throws IOException if error writing to document
243: */
244: protected void writePrefix(int index) throws IOException {
245: try {
246: byte[] bytes = null;
247: if (index < m_prefixBytes.length) {
248: bytes = m_prefixBytes[index];
249: } else if (m_extensionBytes != null) {
250: index -= m_prefixes.length;
251: for (int i = 0; i < m_extensionBytes.length; i++) {
252: int length = m_extensionBytes[i].length;
253: if (index < length) {
254: bytes = m_extensionBytes[i][index];
255: break;
256: } else {
257: index -= length;
258: }
259: }
260: }
261: makeSpace(bytes.length);
262: System.arraycopy(bytes, 0, m_buffer, m_fillOffset,
263: bytes.length);
264: m_fillOffset += bytes.length;
265: } catch (NullPointerException ex) {
266: throw new IOException(
267: "Namespace URI has not been declared.");
268: }
269: }
270:
271: /**
272: * Write entity bytes to output.
273: *
274: * @param bytes actual bytes to be written
275: * @param offset starting offset in buffer
276: * @return offset for next data byte in buffer
277: */
278: protected int writeEntity(byte[] bytes, int offset) {
279: System.arraycopy(bytes, 0, m_buffer, offset, bytes.length);
280: return offset + bytes.length;
281: }
282:
283: /**
284: * Append extension namespace URIs to those in mapping.
285: *
286: * @param uris namespace URIs to extend those in mapping
287: */
288: public void pushExtensionNamespaces(String[] uris) {
289: super .pushExtensionNamespaces(uris);
290: byte[][] items = new byte[uris.length][];
291: if (m_extensionBytes == null) {
292: m_extensionBytes = new byte[][][] { items };
293: } else {
294: int length = m_extensionBytes.length;
295: byte[][][] grow = new byte[length + 1][][];
296: System.arraycopy(m_extensionBytes, 0, grow, 0, length);
297: grow[length] = items;
298: m_extensionBytes = grow;
299: }
300: }
301:
302: /**
303: * Remove extension namespace URIs. This removes the last set of
304: * extension namespaces pushed using {@link #pushExtensionNamespaces}.
305: */
306: public void popExtensionNamespaces() {
307: super .popExtensionNamespaces();
308: int length = m_extensionBytes.length;
309: if (length == 1) {
310: m_extensionBytes = null;
311: } else {
312: byte[][][] shrink = new byte[length - 1][][];
313: System
314: .arraycopy(m_extensionBytes, 0, shrink, 0,
315: length - 1);
316: m_extensionBytes = shrink;
317: }
318: }
319:
320: /**
321: * Request output indent. Output the line end sequence followed by the
322: * appropriate number of indent characters.
323: *
324: * @param bias indent depth difference (positive or negative) from current
325: * element nesting depth
326: * @throws IOException on error writing to document
327: */
328: public void indent(int bias) throws IOException {
329: if (m_indent) {
330: int length = m_indentBase + (getNestingDepth() + bias)
331: * m_indentPerLevel;
332: if (length > m_indentSequence.length) {
333: int use = Math.max(length, m_indentSequence.length * 2
334: - m_indentBase);
335: byte[] grow = new byte[use];
336: System.arraycopy(m_indentSequence, 0, grow, 0,
337: m_indentSequence.length);
338: for (int i = m_indentSequence.length; i < use; i++) {
339: grow[i] = grow[m_indentBase];
340: }
341: m_indentSequence = grow;
342: }
343: makeSpace(length);
344: System.arraycopy(m_indentSequence, 0, m_buffer,
345: m_fillOffset, length);
346: m_fillOffset += length;
347: }
348: }
349:
350: /**
351: * Request output indent. Output the line end sequence followed by the
352: * appropriate number of indent characters for the current nesting level.
353: *
354: * @throws IOException on error writing to document
355: */
356: public void indent() throws IOException {
357: indent(0);
358: }
359:
360: /**
361: * Flush document output. Forces out all output generated to this point.
362: *
363: * @throws IOException on error writing to document
364: */
365: public void flush() throws IOException {
366: if (m_stream != null) {
367:
368: // only flush buffered data, not the actual stream
369: indent();
370: m_stream.write(m_buffer, 0, m_fillOffset);
371: m_fillOffset = 0;
372: }
373: }
374:
375: /**
376: * Close document output. Completes writing of document output, including
377: * closing the output medium.
378: *
379: * @throws IOException on error writing to document
380: */
381: public void close() throws IOException {
382: flush();
383: if (m_stream != null) {
384: m_stream.close();
385: m_stream = null;
386: }
387: }
388:
389: /**
390: * Reset to initial state for reuse. This override of the base class
391: * method handles clearing the internal buffer when starting a new
392: * document.
393: */
394: public void reset() {
395: super .reset();
396: m_fillOffset = 0;
397: }
398: }
|