1/*
2 * Copyright (c) 2006, Oracle and/or its affiliates. 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.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle 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 Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.xml.internal.stream.writers;
27
28import java.io.IOException;
29import java.io.Writer;
30import com.sun.org.apache.xerces.internal.util.XMLStringBuffer;
31
32/**
33 * XMLWriter
34 *
35 * <code>XMLWriter</code> is not thread safe.
36 *
37 * For efficiency this writer buffers the input. Use <code>flush()</code> function
38 * to explicitly write the data to underlying stream.
39 *
40 * This writer is designed in such a way that it atleast buffers the input to the
41 * <code>size</code> specified. Unless <code>flush</code> is called, it guarantees that
42 * data in chunks of size equal to or more than <code>size</code> specified will be written.
43 *
44 *
45 * <code>XMLWriter</code> instance can be reused. <code>setWriter()</code> internally clears the
46 * buffer and stores the reference to newly supplied <code>Writer</code> instance.
47 *
48 * @author Neeraj Bajaj Sun Microsystems, inc.
49 */
50public class XMLWriter extends Writer {
51
52    private Writer writer ;
53    private int size ;
54    //keep the size of internal buffer more than 'size' required to avoid resizing
55    private XMLStringBuffer buffer = new XMLStringBuffer(6 * (1 << 11) ); // 6 KB
56    private static final int THRESHHOLD_LENGTH = 1 << 12 ; // 4 KB
57    private static final boolean DEBUG = false;
58
59    /** Creates the instance of <code>XMLWriter</code>
60     */
61
62     public XMLWriter(Writer writer){
63         this(writer, THRESHHOLD_LENGTH);
64     }
65
66     /**
67      * Creates the instnace of <code>XMLWriter</code>.
68      *
69      * atleast buffers the input to the
70      * <code>size</code> specified.
71      */
72     public XMLWriter(Writer writer, int size){
73         this.writer = writer ;
74         this.size = size;
75     }
76
77     /**
78     * Write a single character.  The character to be written is contained in
79     * the 16 low-order bits of the given integer value; the 16 high-order bits
80     * are ignored.
81     *
82     * <p> Subclasses that intend to support efficient single-character output
83     * should override this method.
84     *
85     * @param c  int specifying a character to be written.
86     * @exception  IOException  If an I/O error occurs
87     */
88
89    public void write(int c) throws IOException {
90        ensureOpen();
91        buffer.append((char)c);
92        conditionalWrite();
93    }
94
95    /**
96     * Write an array of characters.
97     *
98     * @param  cbuf  Array of characters to be written
99     *
100     * @exception  IOException  If an I/O error occurs
101     */
102
103    public void write(char cbuf[]) throws IOException {
104        write(cbuf, 0, cbuf.length);
105    }
106
107    /**
108     * Write a portion of an array of characters.
109     *
110     * @param  cbuf  Array of characters
111     * @param  off   Offset from which to start writing characters
112     * @param  len   Number of characters to write
113     *
114     * @exception  IOException  If an I/O error occurs
115     */
116
117    public void write(char cbuf[], int off, int len) throws IOException{
118        ensureOpen();
119        //optimization: if data size to be written is more than the 'size' specified,
120        //do not buffer the data but write the data straight to the underlying stream
121        if(len > size){
122            //first write the data that may be present in the buffer
123            writeBufferedData();
124            //write directly to stream
125            writer.write(cbuf, off, len);
126        }else{
127            buffer.append(cbuf, off, len);
128            conditionalWrite();
129        }
130    }
131
132    /**
133     * Write a portion of a string.
134     *
135     * @param  str  A String
136     * @param  off  Offset from which to start writing characters
137     * @param  len  Number of characters to write
138     *
139     * @exception  IOException  If an I/O error occurs
140     */
141    public void write(String str, int off, int len) throws IOException {
142        write(str.toCharArray(), off, len);
143    }
144
145    /**
146     * Write a string.
147     *
148     * @param  str  String to be written
149     *
150     * @exception  IOException  If an I/O error occurs
151     */
152    public void write(String str) throws IOException {
153        //optimization: if data size to be written is more than the 'size' specified,
154        //do not buffer the data but write the data straight to the underlying stream - nb.
155        if(str.length() > size){
156            //first write the data that may be present in the buffer
157            writeBufferedData();
158            //write directly to stream
159            writer.write(str);
160        }else{
161            buffer.append(str);
162            conditionalWrite();
163        }
164    }
165
166    /**
167     * Close the stream, flushing it first.  Once a stream has been closed,
168     * further write() or flush() invocations will cause an IOException to be
169     * thrown.  Closing a previously-closed stream, however, has no effect.
170     *
171     * @exception  IOException  If an I/O error occurs
172     */
173    public void close() throws IOException {
174        if(writer == null) return;
175        //flush it first
176        flush();
177        writer.close();
178        writer = null ;
179    }
180
181    /**
182     * Flush the stream.  If the stream has saved any characters from the
183     * various write() methods in a buffer, write them immediately to their
184     * intended destination.  Then, if that destination is another character or
185     * byte stream, flush it.  Thus one flush() invocation will flush all the
186     * buffers in a chain of Writers and OutputStreams.
187     *
188     * @exception  IOException  If an I/O error occurs
189     */
190
191    public void flush() throws IOException {
192        ensureOpen();
193        //write current data present in the buffer
194        writeBufferedData();
195        writer.flush();
196    }
197
198    /** Reset this Writer.
199     *
200     * see @setWriter()
201     */
202    public void reset(){
203        this.writer = null;
204        buffer.clear();
205        this.size = THRESHHOLD_LENGTH;
206    }
207
208    /**
209     * Set the given <code>Writer</code>.
210     *
211     * @param Writer Writer.
212     */
213    public void setWriter(Writer writer){
214        this.writer = writer;
215        buffer.clear();
216        this.size = THRESHHOLD_LENGTH;
217    }
218
219    /** Set the given <code>Writer</code>
220     *
221     * @param Writer Writer.
222     * @param int    Writer will buffer the character data size, after that data is written to stream.
223     */
224    public void setWriter(Writer writer, int size){
225        this.writer = writer;
226        this.size = size;
227    }
228
229   /**
230     * Returns underlying <code>Writer</code>
231     */
232    protected Writer getWriter() {
233        return writer;
234    }
235
236    /** write the buffer data, if the buffer size has increased the size specified
237     */
238    private void conditionalWrite() throws IOException {
239        if(buffer.length > size){
240            if(DEBUG){
241                System.out.println("internal buffer length " + buffer.length + " increased size limit : " + size);
242                System.out.println("Data: ('" + new String(buffer.ch, buffer.offset, buffer.length) + "')");
243            }
244            writeBufferedData();
245        }
246    }
247
248    /** Write the data present in the buffer to the writer.
249     *  buffer is cleared after write operation.
250     */
251    private void writeBufferedData() throws IOException {
252        writer.write(buffer.ch, buffer.offset, buffer.length);
253        buffer.clear();
254    }
255
256    /** Check to make sure that the stream has not been closed */
257    private void ensureOpen() throws IOException {
258        if (writer == null)throw new IOException("Stream closed");
259    }
260}
261